Qamomile v0.13.0では、SELECT操作とglobal phase操作を追加しました。さらに、qmc.controlでは、整数のcontrol_valueを指定できるようになりました。Qiskitと回路描画機能はoptionalになったため、引き続きQiskitを利用する場合はpip install "qamomile[qiskit]"を実行してください。
pip install qamomile==0.13.0破壊的変更¶
Qiskitがoptional dependencyになりました。
pip install qamomile==0.13.0では変換先非依存のcompiler coreをインストールします。本リリースノートの例で使うQiskitや回路描画を追加するには、pip install "qamomile[qiskit,visualization]==0.13.0"を実行してください(#599)。class-basedなcomposite gate APIを削除しました。
CompositeGateやQFT、IQFTなどはpublic APIではなくなりました。名前付きの再利用可能な本体は@qmc.composite_gateで定義します。このdecoratorが返すのは通常のQKernelなので、他の量子カーネルと同じbuild()、draw()、estimate_resources()、qmc.control、qmc.inverseを使います。状態準備のモジュールを
qamomile.circuit.algorithm.state_preparationからqamomile.circuit.stdlib.state_preparationへ移動しました。top-levelのqmc.amplitude_encoding、qmc.amplitude_encoding_from_angles、qmc.computational_basis_stateは引き続き利用できます(#571、#586)。qmc.amplitude_encodingは、方式を指定しない状態準備APIになりました。 エンジンがnativeな状態準備を提供する場合はそれを利用し、提供しない場合はMöttönenの状態準備を使います。Möttönenの状態準備を明示的に扱いたい場合は、qmc.mottonen_amplitude_encodingを使ってください(#602)。resource estimateのデータモデルを変更しました。
GateCount、count_gates、qubits_counter、qamomile.circuit.estimator.algorithmicの各moduleを削除しました。qkernel.estimate_resources()またはqmc.estimate_resources()と、width、gate、depth、call、assumption、estimate qualityを持つ新しいResourceEstimateフィールドを使ってください。互換aliasのestimate.qubits、estimate.gates.t_gates、estimate.gates.clifford_gates、estimate.gates.rotation_gatesは残っていますが、直接構築する場合の引数とto_dict()のschemaは変わりました(#571)。Block単位のJSONとMessagePack serializationを削除しました。
qamomile.circuit.ir.serializeからdump_json、load_json、dump_msgpack、load_msgpack、to_dict、from_dictを公開しなくなり、v0.12.7のpayloadを移行する互換処理もありません。未bindの量子カーネルを決定的なProtobuf byte列として保存するには、qamomile.circuit.serialization.serialize()とdeserialize()を使ってください(#595)。エンジンのemitter classとartifact classはsupported public APIではなくなりました。
QuriPartsEmitPass、CudaqEmitPass、CudaqKernelEmitter、CudaqKernelArtifact、BoundCudaqKernelArtifactを各packageのexportから削除しました。引き続きQiskitTranspiler、QuriPartsTranspiler、CudaqTranspilerと対応するExecutorは使用可能です(#586)。SliceBorrowViolationErrorを削除しました。 fold後のslice borrow失敗は、semanticな原因に応じてQubitBorrowConflictErrorまたはQubitConsumedErrorを送出します。control flowをまたぐ未対応のownership伝播はValidationErrorになります(#591)。
新機能¶
構造を表す厳密な数学式¶
qmc.log2とqmc.ceilは、量子カーネル内にtarget非依存の古典式を構築します。trace中の値をhost Pythonで評価せずに、register幅などのcompile-time構造を導出できます。具体値は即座にfoldし、未bindの入力はresource estimateで厳密な式として保持され、transpile時のbindingsで引数を指定すると解決されます。これらの式は回路構造を決めるため、引数はruntime backend parameterではありません。
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def logarithmic_register(size: qmc.UInt) -> qmc.Vector[qmc.Bit]:
width = qmc.ceil(qmc.log2(size))
return qmc.measure(qmc.qubit_array(width, name="register"))
transpiler = QiskitTranspiler()
executable = transpiler.transpile(
logarithmic_register,
bindings={"size": 9},
)Semanticなqmc.selectと値指定のcontrol¶
qmc.select([U0, U1, ...])は、LSB-firstのindex registerが整数iを表すときに、共有するtarget量子ビットへcase Uiを適用するSELECT操作を作ります。index幅はcase数から推論できるほか、Pythonのintまたはqmc.UIntで指定できます。また、qmc.control(..., control_value=v)は、flattenした制御量子ビットが具体的なLSB-firstの値vと一致するときに対象の操作を実行する制御演算を作ります(#597)。
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def select_example() -> tuple[qmc.Bit, qmc.Bit]:
index = qmc.qubit(name="index")
target = qmc.qubit(name="target")
index = qmc.x(index) # case 1を選択
target = qmc.x(target)
index, target = qmc.select([qmc.x, qmc.z])(index, target)
return qmc.measure(index), qmc.measure(target)
transpiler = QiskitTranspiler()
executable = transpiler.transpile(select_example)再利用可能な操作でglobal phaseを厳密に保持¶
qmc.global_phase(target, phase)はtargetを適用した後にexp(i * phase)を掛け、入れ子の量子カーネル呼び出し、control、inverse、power、structured control flow、Protobufのround tripを通して位相を明示的に保持します。単独のglobal phaseは観測できませんが、制御演算やSELECT演算の下では相対位相となり、干渉結果に現れます(#593、#596)。
import math
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def identity(qubit: qmc.Qubit) -> qmc.Qubit:
return qubit
@qmc.qkernel
def phased_identity(qubit: qmc.Qubit) -> qmc.Qubit:
return qmc.global_phase(identity, math.pi)(qubit)
@qmc.qkernel
def phase_kickback() -> tuple[qmc.Bit, qmc.Bit]:
control = qmc.h(qmc.qubit(name="control"))
target = qmc.h(qmc.qubit(name="target"))
control, target = qmc.control(phased_identity)(control, target)
control = qmc.h(control)
target = qmc.h(target)
return qmc.measure(control), qmc.measure(target)
transpiler = QiskitTranspiler()
executable = transpiler.transpile(phase_kickback)ループをまたぐスカラー値と古典比較¶
qmc.rangeとqmc.itemsの中で同じ型のUIntまたはFloatを再代入すると、ValidationErrorを送出せず、更新した値を次の反復へ渡せるようになりました。accumulatorや、後続のgate parameterまたはコンパイル時分岐に使う値を計算できます。実行時に反復回数が決まるwhileでの持ち越し、測定結果に基づくBitの持ち越し、型の異なる再代入、測定結果に依存する値を量子操作も含むループ内で持ち越す処理はサポートしていません。また、Bit、UInt、Floatハンドルは、トレース時にPythonのbooleanへ変換されず、対応するPython比較演算子を使えるようになりました。数値の大小比較、等値・非等値、UIntとFloatの混在比較、BitとBit・bool・0または1の整数・UIntとの等値比較をサポートします(#580、#587)。
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def accumulated_branch(n: qmc.UInt) -> qmc.Bit:
qubit = qmc.qubit(name="qubit")
total = 0
for i in qmc.range(n):
total = total + i
if total >= 3:
qubit = qmc.x(qubit)
return qmc.measure(qubit)
transpiler = QiskitTranspiler()
executable = transpiler.transpile(accumulated_branch, bindings={"n": 3})量子ビットを保持する射影とreset操作¶
qmc.project_x、qmc.project_y、qmc.project_zでは、射影後の量子ビットと古典Bitの両方を返します。このため、測定後の状態をそのまま使い続けられます。qmc.resetは同じ量子ビットを|0>にした新しいhandleを返し、qmc.measure_resetはZ基底の射影とresetをまとめて実行します。入力handleをconsumeし、返された量子ビットhandleへ状態を引き継ぐため、Qamomileのaffine ownership checkを維持します(#599)。
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def measure_and_reset() -> tuple[qmc.Bit, qmc.Bit]:
qubit = qmc.x(qmc.qubit(name="qubit"))
qubit, before = qmc.measure_reset(qubit)
after = qmc.measure(qubit)
return before, after
transpiler = QiskitTranspiler()
executable = transpiler.transpile(measure_and_reset)汎用APIとMöttönen方式を指定するamplitude encoding¶
qmc.amplitude_encodingは、エンジンでの合成方法を規定せず、準備する状態を表すようになりました。qmc.mottonen_amplitude_encodingは明示的にMöttönenのアルゴリズムを利用します(#602)。
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def prepare_bell() -> qmc.Vector[qmc.Bit]:
qubits = qmc.qubit_array(2, name="qubits")
qubits = qmc.mottonen_amplitude_encoding(
qubits, [1.0, 0.0, 0.0, 1.0]
)
return qmc.measure(qubits)
transpiler = QiskitTranspiler()
executable = transpiler.transpile(prepare_bell)Möttönen Amplitude Encodingを参照してください。
統一したcallable、opaque Oracle、拡張した標準ライブラリ¶
名前付きcomposite gateは通常の量子カーネルになり、直接呼び出し、control、inverse、serialization、visualization、本体からのresource estimateに1つのcall modelを使います。qmc.opaque(name, ...)とqmc.Oracleは、本体を持たないcallableとしてtop-down設計に利用できます。target実装またはsubstitutionを指定するまで、固定またはcontext依存のresource costを保持できます。ResourceEstimateは論理width、gate category、depth、call・query数、assumption、説明用trace、estimate qualityを返し、未bindの入力はsymbolicな式として扱います。また、publicな標準ライブラリには、qmc.mcx、reversibleなripple-carry・modular算術、Grover search helper、Shor order findingを追加しました。状態準備helperも同じcallable modelを使うため、トランスパイルとresource estimateが同じ実行可能な本体を参照します(#571、#586)。
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def stdlib_mcx() -> tuple[qmc.Vector[qmc.Bit], qmc.Bit]:
controls = qmc.x(qmc.qubit_array(3, name="controls"))
target = qmc.qubit(name="target")
controls, target = qmc.mcx(controls, target)
return qmc.measure(controls), qmc.measure(target)
estimate = stdlib_mcx.estimate_resources()
transpiler = QiskitTranspiler()
executable = transpiler.transpile(stdlib_mcx)Protobufによる量子カーネルのserialization¶
qamomile.circuit.serialization.serialize()は、staticで未bindの量子カーネルを決定的なProtobuf byte列として書き出します。public signature、階層的な本体、callable definition、default値、annotation、global phase操作、値の関係を保持します。deserialize()は、通常のtranspiler interfaceから新しいcompile-time bindingとruntime parameterを受け取れる量子カーネル相当のobjectを復元します。bind済みまたはlowering済みのartifact、runtime value、エンジンのobjectはformatに含まれません(#595、#596)。
import qamomile.circuit as qmc
from qamomile.circuit.serialization import deserialize, serialize
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def serializable_flip() -> qmc.Bit:
qubit = qmc.x(qmc.qubit(name="qubit"))
return qmc.measure(qubit)
payload = serialize(serializable_flip)
restored = deserialize(payload)
transpiler = QiskitTranspiler()
executable = transpiler.transpile(restored)内部的な変更¶
上記の機能は、target非依存の準備stage、統一したcallable graph、エンジン非依存の回路表現の上に構築されています。これらの境界により、すべてのtargetを同じ実行modelへ押し込めずに、回路SDKとprogram graph targetでQamomileのsemanticsを共有できます。
明示的なcompiler targetとエンジン非依存の回路lowering¶
QamomileCompiler.prepare()は、到達可能なcallable definition、structured control flow、call graph、古典ABIを保持するPreparedModuleを生成します。回路エンジンはnative materializationの前に、これを検証済みCircuitProgramへloweringします。新しいHugrTranspilerは準備済みsemanticsをHUGR packageへ直接トランスパイルします。新しいQurationTranspilerはPyQretを通してmaterializeし、Qurationのresource compilationを公開します。HUGRのdependencyはhugr extraからインストールできますが、現時点でPyQretは別途インストールが必要です(#586)。
import qamomile.circuit as qmc
from qamomile.circuit.transpiler import QamomileCompiler
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def compiler_example() -> qmc.Bit:
qubit = qmc.h(qmc.qubit(name="qubit"))
return qmc.measure(qubit)
prepared = QamomileCompiler().prepare(compiler_example)
transpiler = QiskitTranspiler()
executable = transpiler.transpile(compiler_example)Why: targetがloweringを選ぶまでsemanticなcallとcontrol flowの構造を保持することで、core IRをエンジン非依存に保ち、HUGRではprogram graph構造を維持し、回路エンジンではSDK native objectを構築する前に1つの検証済み境界を共有できます。
Yieldによるbranch merge¶
IfOperationは、mergeする各resultをtrue branchとfalse branchの並列なyield valueとして記録するようになりました。従来のPhiOp pseudo-operationは削除し、iter_merges()とadd_merge()により、compiler pass、serialization、dependency analysis、classical executionで共通する検証済みのaccessor経路を提供します(#570、#574)。
import qamomile.circuit as qmc
from qamomile.qiskit import QiskitTranspiler
@qmc.qkernel
def measurement_branch_merge() -> tuple[qmc.Bit, qmc.Bit]:
condition = qmc.measure(qmc.h(qmc.qubit(name="condition")))
target = qmc.qubit(name="target")
if condition:
target = qmc.x(target)
return condition, qmc.measure(target)
transpiler = QiskitTranspiler()
executable = transpiler.transpile(measurement_branch_merge)Why: 上記のuser-facing featureとは独立した内部構造の変更です。branch resultを明示的なyieldで表すことで、IRをstructured control flowに合わせ、affineなquantum valueがoperandとして二重に数えられるのを防ぎ、compilerの各consumerでmerge semanticsを一貫させます。
バグ修正¶
測定結果から計算した古典出力を失わなくなりました。 測定結果から導いた比較、算術式、論理式を量子カーネルから返す場合、適切なときはhost側の後処理で実行し、
Noneに解決されなくなりました。この処理はdynamic circuitをサポートしないエンジンでも動作します(#546)。制御化した量子カーネル内のcompile-time分岐をemit前に解決します。
qmc.control内でbindingによって決まるifは、未対応の古典control flowとしてエンジンのemit段階へ到達せず、Qiskit、QURI Parts、CUDA-Qで正しいbranchを選択します(#579)。nativeな状態準備が生成するempty-parameter multiplexerでQiskit Aerがcrashしなくなりました。
QiskitExecutorは、入れ子のcontrol-flow blockを含むtranspile済み回路を再帰的に調べ、Aer実行前にparameterが空のmultiplexerだけを分解します。有効なparameter付きmultiplexerは変更しません(#601)。QURI Partsの多重制御gateはcontrol数に対して線形にscaleします。 fallbackは指数的に大きくなるdense unitaryを構築せず、clean ancillaを使うToffoli cascadeを使います。runtime parameterを持つ多重制御rotationと、以前のdense matrix上限を超えるcontrol幅もトランスパイルでき、追加したancillaは実行終了前にuncomputeします(#555)。
compilerとエンジンのcontractを早い段階で一貫して検証します。 不正なscalar、array、dictionary bindingをshapeと型で検証し、runtime parameterの順序を決定的に保ち、canonical identityにNumPy arrayのshapeを含めます。control、inverse、measurement、Pauli evolutionのcheckもQiskit、QURI Parts、CUDA-Q、HUGR、qBraid、Qurationで整合させました(#599)。
ドキュメント¶
チュートリアル04 — 制御ゲートに、値指定のcontrol、SELECT multiplexer、global phaseのsemanticsを追加しました(#593、#597)。
チュートリアル05 — リソース推定を、本体から導出するsymbolic estimateと新しいresource modelに合わせて更新しました(#588)。
チュートリアル06 — 実行モデルで、量子カーネルのreturn order、
Vectorの測定index順序、tuple outcomeのleast-significant-bit conventionを明確にしました(#581)。チュートリアル10 — コンパイルとトランスパイルに、callable invocation、再構築したtranspiler pipeline、if-merge yield、semanticなslice borrow errorを反映しました(#578、#588、#592)。
Möttönen Amplitude Encodingで、合成方法に依存しない状態準備と明示的なMöttönen APIを区別し、transpile時のamplitude binding、runtimeでのangle再binding、fidelity check、resource formulaを説明します(#602)。
Qiskit、QURI Parts、CUDA-Qのintegration pageを、現在のtranspilerとinstallation APIに合わせて更新しました(#588、#599)。