💫

Pulser入門 - Pasqal版・中性原子量子コンピュータSDKを触ってみよう

に公開

はじめに

前回のBloqade入門では、QuEra社のSDKを使って中性原子量子コンピュータを体験しました。

実は、中性原子量子コンピュータのSDKはもう一つあります。

それがPulser(パルサー)。フランスのPasqal社が開発したオープンソースのPython SDKです。

この記事では、ストロンチウム原子が大好きな著者が、

  • PulserとBloqadeって何が違うの?
  • Pulserの基本的な使い方
  • 同じ問題を両方で解いて比較

を解説します。


第1章: PulserとBloqade、何が違う?

2大SDKの比較

中性原子量子コンピュータには、2つの主要なSDKがあります。

項目 Pulser Bloqade
開発元 Pasqal(フランス) QuEra(アメリカ)
実機 Pasqal QPU QuEra Aquila
クラウド Pasqal Cloud AWS Braket
設計思想 物理寄り・低レベル 抽象化・高レベル
エミュレータ QuTiP連携 独自実装

どっちを使うべき?

正直、どっちでもいいです。

ただ、以下のような傾向があります:

  • Pulser向き: 物理をしっかり理解したい人、細かいパルス制御をしたい人
  • Bloqade向き: 素早くプロトタイピングしたい人、AWS環境がある人

両方触ってみて、肌に合う方を選ぶのがおすすめです。

Pasqal社について

Pasqalは2019年にフランスで設立された量子コンピューティング企業です。

  • 共同創業者の一人は2022年ノーベル物理学賞受賞者のAlain Aspect氏
  • 欧州最大級の中性原子量子コンピュータを開発中
  • 2024年に1000原子を達成、10,000量子ビットを目指すロードマップを発表

第2章: Pulserをインストールする

インストール

pip install pulser

これだけ。Bloqadeと同じくらい簡単。

確認:

import pulser
print(pulser.__version__)

最初のプログラム:原子を配置する

Pulserでは、まず「Register」(レジスタ)を定義します。これが原子の配置です。

import numpy as np
from pulser import Register

# 原子を3つ、一直線に配置
coords = [(0, 0), (6, 0), (12, 0)]  # 座標 (μm)
reg = Register.from_coordinates(coords, prefix="atom")

# 可視化
reg.draw()

BloqadeのChain(3, lattice_spacing=6.0)に相当します。

いろんな配置

Pulserには便利な配置生成メソッドがあります。

from pulser import Register

# 正方格子
square_reg = Register.square(3, spacing=6.0)  # 3x3

# 三角格子
triangle_reg = Register.triangular_lattice(3, 3, spacing=6.0)

# ハニカム格子
honeycomb_reg = Register.hexagonal_lattice(2, 2, spacing=6.0)

# それぞれ表示
square_reg.draw()
triangle_reg.draw()
honeycomb_reg.draw()

第3章: パルスシーケンスを組む

デバイスの選択

Pulserでは、まず使用する「デバイス」を選びます。デバイスによって、使えるパラメータの範囲が決まります。

from pulser import Sequence
from pulser.devices import AnalogDevice

# Registerを作成
coords = [(0, 0), (6, 0), (12, 0)]
reg = Register.from_coordinates(coords)

# シーケンスを作成
seq = Sequence(reg, AnalogDevice)

print(seq.available_channels)

AnalogDeviceは汎用的な設定で、シミュレーション用途に適しています。

チャンネルの宣言

次に、使用するチャンネルを宣言します。

# Rydbergグローバルチャンネルを宣言
seq.declare_channel("rydberg_global", "rydberg_global")

パルスの追加

from pulser import Pulse
from pulser.waveforms import RampWaveform, ConstantWaveform, CompositeWaveform

# パルスを定義(デバイスの最大振幅は約12.5 rad/μs)
rise = RampWaveform(100, 0, 10)      # 100ns で 0→10 rad/μs
hold = ConstantWaveform(1000, 10)    # 1000ns で 10 rad/μs を維持
fall = RampWaveform(100, 10, 0)      # 100ns で 10→0 rad/μs

# パルスを結合(CompositeWaveformを使用)
pulse = Pulse(
    amplitude=CompositeWaveform(rise, hold, fall),
    detuning=ConstantWaveform(1200, 0),  # デチューニングは0
    phase=0
)

# シーケンスに追加
seq.add(pulse, "rydberg")

# 可視化
seq.draw()

実行すると、パルスシーケンスの波形が表示されます:

パルスシーケンス

この辺りがBloqadeより物理寄りな設計です。Bloqadeでは.piecewise_linear()で簡潔に書けることを、Pulserでは明示的に波形を組み立てます。


第4章: シミュレーションを実行する

QuTiPバックエンド

PulserはQuTiP(Quantum Toolbox in Python)をバックエンドとして使います。

from pulser_simulation import QutipEmulator

# エミュレータを作成
sim = QutipEmulator.from_sequence(seq)

# シミュレーション実行
results = sim.run()

# 最終状態を取得
final_state = results.get_final_state()
print(final_state)

サンプリング(測定)

# 1000回サンプリング
counts = results.sample_final_state(N_samples=1000)
print(counts)

実行結果:

Simulation result (1000 samples):
  101: 335
  100: 191
  001: 170
  010: 150
  000: 124
  110: 15
  011: 12
  111: 3
  • 0 = 基底状態(|g⟩)
  • 1 = リュードベリ状態(|r⟩)

101が最多なのは、両端の原子がリュードベリ状態になりやすく、真ん中は封鎖されているためです。


第5章: BloqadeとPulserを比較する

同じ問題を両方で解く

3原子の鎖でリュードベリ励起を行い、結果を比較してみましょう。

Bloqade版

from bloqade.analog.atom_arrangement import Chain

program = (
    Chain(3, lattice_spacing=6.0)
    .rydberg.rabi.amplitude.uniform
    .piecewise_linear([0.1, 1.0, 0.1], [0, 15, 15, 0])
)

result = program.bloqade.python().run(shots=1000)
print(result.report().counts()[0])

Pulser版

from pulser import Register, Sequence, Pulse
from pulser.devices import AnalogDevice
from pulser.waveforms import RampWaveform, ConstantWaveform, CompositeWaveform
from pulser_simulation import QutipEmulator

# Register
coords = [(0, 0), (6, 0), (12, 0)]
reg = Register.from_coordinates(coords, prefix="q")

# Sequence
seq = Sequence(reg, AnalogDevice)
seq.declare_channel("rydberg", "rydberg_global")

# Pulse (時間はnsec単位、振幅は最大12.5 rad/μs)
rise = RampWaveform(100, 0, 10)
hold = ConstantWaveform(1000, 10)
fall = RampWaveform(100, 10, 0)

pulse = Pulse(
    amplitude=CompositeWaveform(rise, hold, fall),
    detuning=ConstantWaveform(1200, 0),
    phase=0
)
seq.add(pulse, "rydberg")

# Simulation
sim = QutipEmulator.from_sequence(seq)
results = sim.run()
counts = results.sample_final_state(N_samples=1000)
print(counts)

コード量の比較

項目 Bloqade Pulser
行数 約7行 約20行
抽象度 高い 低い
柔軟性 中程度 高い

Bloqadeはメソッドチェーンでシンプルに書けます。
Pulserは明示的で、何をしているか分かりやすい。

どちらが良いかは好みと用途次第。


第6章: MIS問題を解く

Pulserでの実装

from pulser import Register, Sequence, Pulse
from pulser.devices import AnalogDevice
from pulser.waveforms import InterpolatedWaveform
from pulser_simulation import QutipEmulator
import numpy as np

# 3x3の正方格子
reg = Register.square(3, spacing=6.5, prefix="q")
reg.draw()

# シーケンス作成
seq = Sequence(reg, AnalogDevice)
seq.declare_channel("rydberg", "rydberg_global")

# 断熱的パルス(MIS用)
duration = 2600  # ns
t = np.linspace(0, duration, 100)

# Ω: 0 → 10 → 0(最大振幅は約12.5 rad/μs)
omega = 10 * np.sin(np.pi * t / duration) ** 2

# Δ: -10 → 10
delta = -10 + 20 * t / duration

pulse = Pulse(
    amplitude=InterpolatedWaveform(duration, omega),
    detuning=InterpolatedWaveform(duration, delta),
    phase=0
)

seq.add(pulse, "rydberg")
seq.draw()

# シミュレーション
sim = QutipEmulator.from_sequence(seq)
results = sim.run()
counts = results.sample_final_state(N_samples=1000)

# 結果表示
sorted_counts = sorted(counts.items(), key=lambda x: x[1], reverse=True)
print("=== 出現頻度の高い状態 ===")
for state, count in sorted_counts[:5]:
    print(f"  {state}: {count}回 (励起数: {state.count('1')})")

実行結果:

=== 出現頻度の高い状態 ===
  101000101: 269回 (励起数: 4)
  101010101: 204回 (励起数: 5)
  001100001: 46回 (励起数: 3)
  010000101: 45回 (励起数: 3)
  100000101: 37回 (励起数: 3)

MIS結果

101010101(チェッカーボードパターン)と101000101が上位に来ています。これらは隣り合う原子が同時に励起されていない、MIS問題の解候補です。


第7章: Pasqal Cloudへの接続(発展編)

Pasqalも実機へのクラウドアクセスを提供しています。

from pulser_pasqal import PasqalCloud

# Pasqal Cloudに接続(要アカウント)
connection = PasqalCloud(
    username="your_email",
    password="your_password",
    project_id="your_project_id"
)

# ジョブ投入
job = connection.submit(seq, job_params={"runs": 100})

# 結果取得
result = job.result

おわりに

この記事で学んだこと

  1. PulserとBloqadeの違いと使い分け
  2. Pulserでの原子配置(Register)の定義
  3. パルスシーケンスの構築方法
  4. QuTiPエミュレータでのシミュレーション
  5. MIS問題の実装
  6. 両SDKの比較

どっちを選ぶ?

  • 手軽に始めたい → Bloqade
  • 物理を深く理解したい → Pulser
  • AWS環境がある → Bloqade
  • 研究で使いたい → 両方触ってみる

参考リンク


GitHubで編集を提案

Discussion