Sari la conținutul principal
Acest proiect este în versiune beta. Ai găsit o eroare? Deschide un issue pe GitHub. Ai o întrebare sau o idee? Întreabă în Discussions — ne-ar plăcea să aflăm părerea ta!
Cum este calitatea traducerii pe această pagină?

Transpilează circuite bosonice

Acest ghid explică cum să transpilezi un BosonicCircuit într-un QuantumCircuit standard. Un circuit bosonic nu poartă nicio trunchiere, nicio codificare și niciun număr de qubiți (vezi Lucrează cu circuite bosonice), deci transpiler-ul este locul unde se fac aceste alegeri. Trunchierea și codificarea sunt descrise de un LevelSpec (vezi Descrie trunchierea cu un LevelSpec), iar mapperul care transformă operatorii în operatori qubit este descris în Mapează reprezentările operatorilor. Rezultatul este un QuantumCircuit obișnuit, deci orice simulator compatibil cu Qiskit îl poate rula.

Ghidul folosește un singur exemplu mic de la un capăt la altul: două moduri bosonice, o stare cu un boson în fiecare și o singură evoluție în timp sub un hamiltonian Bose-Hubbard ai cărui termeni de legătură și on-site sunt în grupuri separate (vezi Grupează termenii operatorului: folosește structura operatorului):

>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution, InitializeModes
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> terms = {(cre(0), ann(1)): -1.0, (cre(1), ann(0)): -1.0}
>>> terms.update({(cre(mode), cre(mode), ann(mode), ann(mode)): 0.5 for mode in range(2)})
>>> hamiltonian = BosonOperator.from_dict(terms)
>>> # the bond is group 0 and the on-site interaction is group 1; derive the index from the term,
>>> # because the stored order of a dictionary is not specified
>>> hamiltonian.groups = [
... 0 if len({mode for _, mode in term}) == 2 else 1 for term, _ in hamiltonian.iter_terms()
... ]
>>>
>>> circuit = BosonicCircuit(2)
>>> circuit.append(InitializeModes([1, 1]), circuit.modes)
>>> circuit.append(Evolution(2, hamiltonian, time=0.1), circuit.modes)
>>> dict(circuit.count_ops())
{'InitializeModes': 1, 'Evolution': 1}

Etapele transpilării​

Așa cum se explică în qiskit_bosons.transpiler, pass manager-ele presetate din acest pachet împart transpilarea în următoarele etape:

Intrare​

Convertește circuitul într-o structură de date graf orientat aciclic (DAG), reprezentarea pe care lucrează toate trecerile ulterioare.

Optimizare​

Păstrează circuitul în spațiul bosonic. Aceste treceri folosesc structura operatorilor bosonici, în special grupurile lor, în timp ce nu există încă nicio informație despre qubiți. B2BSynthesis este trecerea care descompune aici porțile, cu un plugin pentru fiecare tip de poartă: o formulă de produs sau qDRIFT pentru un Evolution și rețeaua Clements pentru un ModeRotation (vezi Sintetizează o poartă Evolution în spațiul bosonic pentru cum se alege o metodă). RelabelModes reordonează modurile. Presetul descompune rotațiile de moduri în această etapă și lasă restul neschimbat.

Aranjare​

Mapează fiecare registru de moduri bosonice pe un registru cuantic. Un mod bosonic ocupă în general mai mulți qubiți (dd dintre ei sub codificările unare, ⌈log⁡2d⌉\lceil \log_2 d \rceil sub cele compacte), deci registrul cuantic este mai lat decât registrul de moduri. OccupationB2QLayout derivă lățimea registrului dintr-un LevelSpec.

Sinteză​

Convertește porțile bosonice în operații pe qubiți, câte un plugin pentru fiecare tip de poartă. Codificarea are loc în acest pas: pluginul pentru o poartă Evolution mapează operatorul ei într-un operator qubit, iar pluginul pentru o poartă InitializeModes setează qubiții care codifică fiecare ocupare. O barieră este transferată pe fiecare qubit al fiecărui registru pe care l-a atins.

Qubit​

Transpilează circuitul de qubiți rezultat cu metodele proprii ale Qiskit, de exemplu către un set de porți de bază sau un dispozitiv. Presetul îl include întotdeauna; implicit este pass manager-ul presetat al Qiskit, care sintetizează fiecare evoluție în porți (vezi Continuă la nivelul qubit).

Ieșire​

Convertește structura de date DAG internă înapoi într-un QuantumCircuit.

Trunchierea și codificarea nu sunt stocate în circuit. Ele sunt date transpiler-ului, iar layout-ul, pregătirea stării și mapperul trebuie să fie toate de acord asupra lor. Presetul asigură acest lucru predând același LevelSpec tuturor celor trei.

Un exemplu practic​

generate_preset_occupation_pass_manager() construiește pipeline-ul dintr-o specificație. Reducerea exemplului sub două codificări schimbă doar specificația. Presetul se termină cu transpilarea proprie a Qiskit la nivel de qubit, care ar sintetiza evoluția în porți. Pentru a vedea circuitul exact așa cum îl emit etapele bosonice, golește mai întâi acea etapă:

>>> from qiskit.transpiler import PassManager
>>> from qiskit_bosons.mappers import LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>>
>>> for encoding in ("unary_unprojected", "gray"):
... pass_manager = generate_preset_occupation_pass_manager(LevelSpec(2, encoding))
... pass_manager.qubit = PassManager()
... lowered = pass_manager.run(circuit)
... print(f"{encoding:18s} qubits={lowered.num_qubits} ops={dict(lowered.count_ops())}")
unary_unprojected qubits=6 ops={'x': 2, 'PauliEvolution': 1}
gray qubits=4 ops={'x': 2, 'PauliEvolution': 1}

Cele două codificări folosesc șase, respectiv patru qubiți (trei niveluri per mod sub codificarea unară, doi qubiți per mod sub codul Gray), dar structura circuitului este aceeași: două operații XGate pregătesc ocupările, iar un PauliEvolutionGate poartă evoluția.

Compară cu fluxul de lucru tradițional​

Ghidul circuite bosonice descrie fluxul de lucru tradițional, care mapează hamiltonianul într-un operator qubit înainte de a construi QuantumCircuit. Cele două căi produc circuite echivalente. Diferă prin ce trebuie refăcut când se schimbă trunchierea. Circuitul bosonic este construit o dată și redus la fiecare limită, în timp ce calea cu mapare întâi necesită remaparea operatorului și reconstruirea circuitului:

>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit.library import PauliEvolutionGate
>>> from qiskit_bosons.mappers.library import occupation_encoding
>>>
>>> basis = ["rz", "sx", "x", "cx"]
>>> cx_counts = []
>>> for max_occupation in (1, 2, 3):
... spec = LevelSpec(max_occupation, "gray")
... # the bosonic circuit is reused as it is
... lowered = generate_preset_occupation_pass_manager(
... spec, optimization_level=0, basis_gates=basis
... ).run(circuit)
... # the map-first route maps the operator and builds a new circuit for every cutoff
... qubit_hamiltonian = occupation_encoding(hamiltonian, spec)
... by_hand = QuantumCircuit(qubit_hamiltonian.num_qubits)
... _ = by_hand.append(PauliEvolutionGate(qubit_hamiltonian, time=0.1), by_hand.qubits)
... cx_counts.append(lowered.count_ops().get("cx", 0))
... print(
... f"d={max_occupation + 1}: {lowered.num_qubits} qubits; map-first: "
... f"{len(qubit_hamiltonian)} Pauli terms to place by hand"
... )
d=2: 2 qubits; map-first: 2 Pauli terms to place by hand
d=3: 4 qubits; map-first: 10 Pauli terms to place by hand
d=4: 4 qubits; map-first: 22 Pauli terms to place by hand

Numărul de porți de încâlcire depinde de cum sintetizează Qiskit poarta de evoluție, ceea ce se schimbă între versiuni, așa că reprezintă-l grafic:

CX count of the lowered circuit against the number of levels d, growing quickly with d.

Numărul crește rapid cu limita, motiv pentru care merită ca limita să fie un parametru al transpilării și nu al circuitului. Cele două căi sunt echivalente aici, dar numai cea bosonică poate și să intercaleze gruparea operatorului cu formula de produs (vezi Sintetizează o poartă Evolution în spațiul bosonic) sau să predea codificarea unei alte specificații fără a schimba o linie din circuit.

O specificație cu o singură limită lasă deschis numărul de moduri, deci un singur pass manager servește circuite de orice lățime. O specificație cu o limită per mod fixează lățimea:

>>> wide = BosonicCircuit(3)
>>> wide.append(InitializeModes([1, 0, 1]), wide.modes)
>>> shared = generate_preset_occupation_pass_manager(LevelSpec(2, "gray"))
>>> shared.run(wide).num_qubits
6
>>> per_mode = generate_preset_occupation_pass_manager(LevelSpec([2, 1], "gray"))
>>> per_mode.run(circuit).num_qubits
3
>>> try:
... per_mode.run(wide)
... except ValueError as error:
... print(error)
the spec covers 2 modes, but 3 were expected

Construiește pipeline-ul manual​

Presetul este un MultiStagePassManager cu o intrare pentru fiecare etapă. Poți asambla același pipeline din părțile sale, ceea ce este punctul de plecare pentru fiecare personalizare de mai jos:

>>> from qiskit.passmanager import MultiStagePassManager
>>> from qiskit_bosons.mappers.library import occupation_encoding
>>> from qiskit_bosons.transpiler import BosonicCircuitToDAG, QuantumDAGToCircuit
>>> from qiskit_bosons.transpiler.passes import (
... B2QSynthesis,
... MapperFnEvolutionSynthesis,
... OccupationB2QLayout,
... OccupationInitializeModesSynthesis,
... )
>>>
>>> spec = LevelSpec(2, "gray")
>>>
>>> def mapper_fn(operator, num_qubits):
... return occupation_encoding(operator, spec, num_qubits)
>>>
>>> synthesis = B2QSynthesis()
>>> synthesis.methods["Evolution"] = MapperFnEvolutionSynthesis(mapper_fn)
>>> synthesis.methods["InitializeModes"] = OccupationInitializeModesSynthesis(spec)
>>>
>>> by_hand = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... layout=OccupationB2QLayout(spec),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... )
>>> preset = generate_preset_occupation_pass_manager(spec)
>>> preset.qubit = PassManager()
>>> by_hand.run(circuit) == preset.run(circuit)
True

Funcția mapper primește un operator și lățimea registrului derivată de layout și returnează un SparseObservable. Deoarece primește lățimea, transmiterea num_qubits către occupation_encoding() face ca mapper-ul să verifice că specificația se potrivește cu registrul, astfel încât o nepotrivire între layout și codificare eșuează cu un mesaj clar.

Fluxuri de lucru avansate​

Furnizează un layout explicit​

CustomB2QLayout înlocuiește etapa de layout cu o mapare furnizată de tine, de la fiecare registru bosonic al circuitului la un registru cuantic. Este utilă atunci când registrul trebuie să fie mai larg decât are nevoie specificația, de exemplu pentru a lăsa qubiți inactivi pentru alte scopuri:

>>> from qiskit.circuit import QuantumRegister
>>> from qiskit_bosons.transpiler.passes import CustomB2QLayout
>>>
>>> (mode_register,) = BosonicCircuitToDAG().run(circuit).qregs.values()
>>> custom = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... layout=CustomB2QLayout({mode_register: QuantumRegister(6, "q")}),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... )
>>> custom.run(circuit).num_qubits
6
Avertisment

Un layout personalizat este responsabilitatea ta. Pluginurile de sinteză adresează în continuare qubiții prin specificație, astfel încât un registru care nu se potrivește cu lățimile specificației produce un circuit greșit sau o eroare în adâncul unui plugin. Un layout personalizat omite și verificarea pe care o efectuează OccupationB2QLayout după RelabelModes (vezi secțiunea următoare).

Reordonează modurile​

RelabelModes este un pass din etapa de optimizare care reordonează modurile. Permutarea este citită ca o mapare de la indexul original la indexul nou al modului și este înregistrată în metadatele circuitului. Reordonarea schimbă qubiții pe care îi ocupă fiecare mod și astfel poate reduce adâncimea circuitului coborât.

Deoarece rezultatul este exprimat în noua ordine a modurilor, șirurile de biți măsurate trebuie mapate înapoi înainte de a fi interpretate. Cu mai mulți qubiți pe mod, maparea acționează asupra blocurilor de qubiți și nu asupra biților individuali. Exemplul următor redenumește trei moduri, fiecare ocupând doi qubiți sub codul Gray, și recuperează ocuparea originală din numărătorile măsurate:

>>> # BasicSimulator is a convenient pure-Python choice; any simulator works
>>> from qiskit.providers.basic_provider import BasicSimulator
>>> from qiskit_bosons.transpiler.passes import RelabelModes
>>>
>>> state = BosonicCircuit(3)
>>> state.append(InitializeModes([2, 0, 1]), state.modes)
>>> spec = LevelSpec(2, "gray") # three levels, two qubits per mode
>>> width = spec.resolve(3).widths[0]
>>> permutation = [2, 0, 1]
>>>
>>> synthesis = B2QSynthesis()
>>> synthesis.methods["InitializeModes"] = OccupationInitializeModesSynthesis(spec)
>>> relabeled = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... optimization=RelabelModes(permutation),
... layout=OccupationB2QLayout(spec),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... ).run(state)
>>> relabeled.metadata
{'permutation': [2, 0, 1]}
>>>
>>> def restore(bitstring, permutation, width):
... """Map a bitstring in the new mode order back to the original mode order."""
... qubits = bitstring[::-1] # character q is now the value of qubit q
... blocks = [qubits[mode * width : (mode + 1) * width] for mode in range(len(permutation))]
... return "".join(blocks[new] for new in permutation)[::-1]
>>>
>>> relabeled.measure_all()
>>> (measured,) = BasicSimulator().run(relabeled, shots=1).result().get_counts()
>>> measured
'110100'
>>> restore(measured, permutation, width)
'010011'

Șirul de biți restaurat este ceea ce produce circuitul fără redenumire, citit în ordinea little-endian a Qiskit: modul 0 deține codul Gray al nivelului doi (11), modul 1 deține nivelul zero (00), iar modul 2 deține nivelul unu (01).

>>> plain = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... layout=OccupationB2QLayout(spec),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... ).run(state)
>>> plain.measure_all()
>>> (reference,) = BasicSimulator().run(plain, shots=1).result().get_counts()
>>> restore(measured, permutation, width) == reference
True
Avertisment

O specificație care dă modurilor limite diferite nu poate fi combinată cu RelabelModes. Limitele sunt atașate modurilor după poziția lor, deci reordonarea modurilor ar pune o limită pe modul greșit. OccupationB2QLayout refuză un circuit redenumit în acest caz:

>>> mixed = LevelSpec([2, 1, 2], "gray")
>>> synthesis = B2QSynthesis()
>>> synthesis.methods["InitializeModes"] = OccupationInitializeModesSynthesis(mixed)
>>> refused = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... optimization=RelabelModes(permutation),
... layout=OccupationB2QLayout(mixed),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... )
>>> try:
... refused.run(state)
... except ValueError as error:
... print(str(error).split(",")[0])
the circuit has been relabeled by RelabelModes

Alege pluginurile de sinteză​

B2QSynthesis nu are pluginuri implicite; presetul înregistrează câte unul pentru fiecare tip de poartă. Pluginurile disponibile sunt gestionate de B2QSynthesisPluginManager, care le listează după numele porții asupra căreia acționează:

>>> from qiskit_bosons.transpiler.passes import B2QSynthesisPluginManager
>>> manager = B2QSynthesisPluginManager()
>>> for gate in manager.op_names():
... print(gate, manager.method_names(gate))
Evolution ['MapperFn']
InitializeModes ['Occupation']

Cele două metode încorporate sunt Evolution.MapperFn și InitializeModes.Occupation. Prima sintetizează o evoluție din orice funcție mapper, ceea ce este modul în care se conectează o codificare personalizată (vezi Map operator representations). Biblioteca nu include niciun plugin pentru o poartă ModeRotation. Presetul o descompune mai întâi în spațiul bosonic în porți Evolution, pe care apoi le tratează prima metodă, dar un plugin înregistrat pentru ModeRotation poate coborî direct poarta; vezi Rotate bosonic modes și Lower a rotation without decomposing it. Poți înregistra o metodă după nume prin argumentul config al B2QSynthesis sau poți scrie un obiect plugin direct în B2QSynthesis.methods, așa cum fac exemplele anterioare.

Continuă la nivelul qubiților​

Presetul se încheie întotdeauna cu o etapă qubit, care este pass manager-ul preset propriu al Qiskit. Argumentele cuvânt cheie ale generate_preset_occupation_pass_manager() sunt transmise către qiskit.transpiler.generate_preset_pass_manager(), astfel încât poți traduce într-un set de porți de bază sau poți ținti direct un dispozitiv. Preferă optimization_level=0 pentru a păstra circuitul apropiat de ceea ce au produs etapele bosonice:

>>> from qiskit.transpiler import generate_preset_pass_manager
>>>
>>> spec = LevelSpec(2, "gray")
>>> translated = generate_preset_occupation_pass_manager(
... spec, optimization_level=0, basis_gates=["rz", "sx", "x", "cx"]
... ).run(circuit)
>>> sorted(translated.count_ops())
['cx', 'rz', 'sx', 'x']

Două alte alegeri sunt frecvente. Atribuie un PassManager gol etapei qubit atunci când vrei să arăți circuitul exact așa cum l-a sintetizat pipeline-ul, cum fac exemplele de mai sus. Sau asamblează tu pipeline-ul și adaugă orice pass manager Qiskit ca etapă qubit a MultiStagePassManager. Etapele rulează în ordinea în care sunt date, deci etapa qubit trebuie să vină înainte de output, deoarece pass-urile Qiskit se așteaptă la un DAG și nu la un circuit finalizat:

>>> synthesis = B2QSynthesis()
>>> synthesis.methods["Evolution"] = MapperFnEvolutionSynthesis(
... lambda operator, num_qubits: occupation_encoding(operator, spec, num_qubits)
... )
>>> synthesis.methods["InitializeModes"] = OccupationInitializeModesSynthesis(spec)
>>> staged = MultiStagePassManager(
... input=BosonicCircuitToDAG(),
... layout=OccupationB2QLayout(spec),
... synthesis=synthesis,
... qubit=generate_preset_pass_manager(
... optimization_level=0, basis_gates=["rz", "sx", "x", "cx"]
... ),
... output=QuantumDAGToCircuit(),
... )
>>> sorted(staged.run(circuit).count_ops())
['cx', 'rz', 'sx', 'x']

Pașii următori​