Open fvoichick opened 10 months ago
We can definitely improve the documentation and warning here, though the current output is as expected / designed.
In order to achieve what you want in the short term, you're likely going to want to create a custom Target
based on the existing one, overriding the heterogeneous ISA with whatever other data you want. I suspect (but am not sure) that what you intended to happen could be achieved with:
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import FakeCairoV2
from qiskit.transpiler import Target
from qiskit.circuit import Measure
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
def make_custom_target(base: Target, basis_gates: list[str]):
name_map = get_standard_gate_name_mapping()
custom_target = Target()
# This gets a coupling map from the union of all 2q links, regardless of
# active gate and directionality.
base_all_2q_coupling = base.build_coupling_map()
base_all_2q_coupling.make_symmetric()
for gate_name in basis_gates:
gate = name_map[gate_name]
if gate.num_qubits == 1:
# Assume that any 1q gate is available as an ideal gate on all
# qubits. Throws away noise information.
custom_target.add_instruction(
gate, {(qubit,): None for qubit in range(base.num_qubits)}
)
elif gate.num_qubits == 2:
# Assume that any 2q gate is availble bidirectionally on all
# pairs of qubits that have _any_ 2q gate defined on them in
# the base. Throws away noise information.
custom_target.add_instruction(
gate, {qargs: None for qargs in base_all_2q_coupling}
)
else:
raise ValueError("don't know what to do with this")
if "measure" not in basis_gates and "measure" in base:
# Assume that the same individual qubits are measurable.
custom_target.add_instruction(
Measure(),
{qargs: None for qargs in base.qargs_for_operation_name("measure")},
)
return custom_target
circuit = QuantumCircuit(1)
circuit.x(0)
result = transpile(
circuit, target=make_custom_target(FakeCairoV2().target, ["rz", "sx", "cx"])
)
print(*result.count_ops().items())
where hopefully I made clear several of the places where we'd have to make questionable assumptions when trying to override a Target
with something like basis_gates
, and places where you might want to tweak exactly what's created.
To explain a bit more:
Passing contradictory data to transpile
like this is generally going to require us to ignore at least one of the two items. Before target
existed, we only had backend
, which was at the time a BackendV1
instance, and most of the rest of the arguments to transpile
are direct fields on BackendV1
. This meant that there was an obvious meaning to supplying both a backend
and the other options; you were overriding a single field of the backend.
With Target
, the same logic is much trickier, because Target
represents far more complex and heterogeneous data than the other individual arguments can. Most of the compiler passes now draw all their data from Target
, and only fall back to the other forms if no Target
was given. For example, a Target
doesn't have a split coupling_map
and basis_gates
, it stores information on which gates are available on which qubit arguments along with the associated errors, which is more general. In this sense, setting target=..., basis_gates=...
in a call to transpile
ends up with knock-on effects if we prioritise basis_gates
; we now also do not know the effective coupling map, because that's not supplied.
As a concrete example, consider a Target
that has:
rz
, sx
available on all 1q argscx
on [(0, 1), (1, 2), (2, 3)]
ecr
on [(3, 4), (4, 5), (5, 6)]
ccx
on [(3, 4, 5), (4, 5, 6)]
Now suppose we're asked to transpile to this target, but overridden so that basis_gates=["rz", "sx", "cx"]
. What should we assume that the "coupling" of the effective target is? There's lots of potential choices here, and it's not at all clear what the meaning should be - we could:
ecr
and toffoli
, which makes qubits 4, 5 and 6 isolatedcx
and ecr
, but then assume that a ccx
also requires triangle connectivity between its arguments, adding in extra (3, 5)
and (4, 6)
links.and in most of those, we also have the question: for any implicit links, what should we assume about the available directionality? Should ecr
on (3, 4)
imply that cx
would be active in the (3, 4)
direction, or maybe the (4, 3)
direction is also valid?
The point I'm trying to make clear is that it's not really meaningful to override single BackendV1
fields on a Target
because of the heterogenous ISA support (which current hardware vendors are starting to have more and more of); it has huge knock-on implications in general. This is why we're suggesting creating your own Target
in these situations.
Note that if the goal is to synthesize a quantum circuit with FakeCairoV2
's coupling map and with custom basis gates, then (exactly as @jakelishman explained) one can simply write
result = transpile(circuit, coupling_map=FakeCairoV2().target.build_coupling_map(), basis_gates=["rz", "sx", "cx"])
Additionally, we have the method Target.from_configuration()
that can build a Target
object given coupling_map
, basis_gates
, etc..
Environment
What is happening?
A
BasisTranslator
does not translate a gate if itstarget
includes the gate. Thus, thetarget_basis
is essentially ignored, and the resulting circuit includes gates that are outside of the suppliedbasis_gates
.How can we reproduce the issue?
Run this code:
Output:
('x', 1)
What should happen?
I expect it to output
('sx', 2)
or something similar, as it does when I omit thetarget
argument. I do not expect it to silently ignore thebasis_gates
argument.Any suggestions?
Fix
BasisTranslator.run
, particularly line 157. If you intend to keep the current behavior, then you should warn users who provide both thetarget
andbasis_gates
(ortarget_basis
) that the presence of the former causes the transpiler to ignore the latter.