こんにちは|こんばんは。カエルのアイコンで活動しております @kyamaz
です。本稿は、2025年QiitaアドベントカレンダーのHaskellのカレンダー向けのエントリです。
はじめに
は量子コンピューター界隈のOSSコミュニティで2016年から活動しております1.プログラミング言語では,Haskellがイチオシの言語で,今年はいろいろな素数をコレクションするのにHaskellを利用して計算させたりして楽しんでいます.
最近は,LEAN言語もお気に入りのプログラミング言語に急浮上してきています.
さて,以前(2017年のHaskellのアドベントカレンダー)に
が『量子コンピューターにはモナドがよく似合う』というエントリを寄稿しました.その後も似たような記事は増えもせず,相変わらず「量子 モナド Haskell」の3ワードでの検索では,一番上にでてくるエントリとなっております.これが今から8年前のことですが,当時はそこそこ "いいね" を頂けたエントリでした.当時は,Haskellのパッケージにある QIO の解説記事でした.ただ,現在の量子計算のブームのかなり前に QIO が実装されたこともあり,QIO に変わる実装を作っていくべきだと
は考えております.
量子計算でモナド
量子計算のモナドの実装例は色々と考えられ,前出の QIO や旧来からの Quipper の実装にも見られます.QIO(Quantum IO Monad)では,QIOが IOモナドのように扱えるということで命名されていて,モナドとして実装されています.Quipper では,Circがモナドとして実装されています.
本稿では,新規にシンプルに量子計算におけるモナドがどのように構成されているかを見ていきたいと思います.そのための実装として,Github にとてもシンプルで良い実装 haskell-quantum がありました.この実装を参考にしたプログラム例を使って,量子コンピューターにはモナドが似合うことを本稿で少し解説していきます.
まずは,拡張機能とmodule宣言やimport文を書いておきます.(特に詳細の解説はしません)量子モナドを定義するモジュールのパッケージは,OpenQL.Quantumとします.
モジュール宣言(Code_1)
{-# LANGUAGE DeriveFunctor #-}
module OpenQL.Quantum
( -- * 量子状態の型
Quantum(..)
, Amplitude
, WaveFunction
-- * 量子状態の生成
, quantize
, superpose
, fromList
-- * 量子ビット
, Qubit(..)
, qubit0
, qubit1
, qplus
, qminus
-- * 量子ゲート
, hadamard
, pauliX
, pauliZ
, sGate
, tGate
, cnot
-- * 測定と観測
, probabilities
, measure
, observe
-- * ユーティリティ
, normalize
, amplitudes
, showQuantum
) where
import Data.Complex
import Data.List (groupBy, sortBy)
import Data.Function (on)
import System.Random (randomRIO)
確率振幅Amplitude,波動関数Wavefunction,量子状態を表す型Quantumを基本的な型として定義します.
基本的な型定義(Code_2)
-- ============================================================
-- 基本的な型定義
-- ============================================================
-- | 確率振幅(複素数)
--
-- 量子力学では、状態は複素数で表される。
-- 測定確率は振幅の絶対値の2乗になる。
type Amplitude = Complex Double
-- | 波動関数: 状態と振幅のペアのリスト
--
-- 例: [(0.5 :+ 0, "up"), (0.5 :+ 0, "down")]
-- は「up」と「down」の重ね合わせを表す。
type WaveFunction a = [(Amplitude, a)]
-- | 量子状態を表す型
--
-- 内部的には波動関数(状態と振幅のリスト)を保持。
-- Monadインスタンスにより、do記法で量子計算を記述できる。
newtype Quantum a = Quantum { runQuantum :: WaveFunction a }
deriving (Show, Functor)
Quantum型をMonad型クラスの定義を満たすようにします.Monad型クラスには,主に2つの演算子が必要です.
-
>>=(バインド演算子):モナド値から値を取り出し,それを使って次のモナド計算を繋げる役割を果たします. -
return(あるいはpure):通常の値をモナドの文脈(「箱」)に格納する役割を果たします.
また,Haskellの標準ライブラリでは,Functor と Applicative のインスタンス定義も必要です.ただしここでの例では,Functorは Code_2 で deriving で宣言されています.なお,Functor, Applicative, Monadについては次のエントリが詳しくて,概念を理解するには参考になります.
Quantumモナドのインスタンス定義(Code_3)
-- ============================================================
-- Monadインスタンス(ここがdo記法を可能にする核心部分)
-- ============================================================
instance Applicative Quantum where
-- | 古典の値を量子状態に持ち上げる
-- 振幅1(確率100%)の単一状態を作る
pure x = Quantum [(1 :+ 0, x)]
-- | 関数の重ね合わせを値の重ね合わせに適用
Quantum fs <*> Quantum xs = Quantum
[ (af * ax, f x)
| (af, f) <- fs
, (ax, x) <- xs
]
-- | Monadインスタンス
--
-- これにより do 記法が使えるようになる。
-- >>= は重ね合わせの各状態に関数を適用し、
-- 結果の振幅を元の振幅と掛け合わせる。
--
-- 直感的には:
--
-- * 入力: 複数の状態の重ね合わせ
-- * 操作: 各状態を新しい重ね合わせに変換
-- * 出力: 全ての可能性を合成した新しい重ね合わせ
instance Monad Quantum where
Quantum xs >>= f = Quantum $ concat
[ [ (amp * amp', y) -- 振幅を掛け合わせる
| (amp', y) <- runQuantum (f x) -- 各状態に関数を適用
]
| (amp, x) <- xs -- 入力の各状態について
]
ここからは,このQuantumモナドを用いて,「汎用的な量子状態の生成」「量子ビットの定義と生成」「量子ゲートの定義」「測定・観測」についてプログラムでみてみましょう.
汎用的な量子状態の生成(Code_4)
-- ============================================================
-- 量子状態の生成
-- ============================================================
-- | 状態と振幅のリストから量子状態を生成
--
-- >>> quantize [(1, True), (0, False)]
-- 100% の確率で True
--
-- >>> quantize [(1/sqrt 2, 0), (1/sqrt 2, 1)]
-- 50%/50% の重ね合わせ
quantize :: [(Double, a)] -> Quantum a
quantize pairs = Quantum [(amp :+ 0, x) | (amp, x) <- pairs]
-- | 複数の状態を等確率で重ね合わせる
--
-- >>> superpose [0, 1, 2]
-- 各状態が 1/√3 の振幅を持つ
superpose :: [a] -> Quantum a
superpose xs = Quantum [(amp, x) | x <- xs]
where
amp = (1 / sqrt (fromIntegral $ length xs)) :+ 0
-- | 波動関数から直接量子状態を生成
fromList :: WaveFunction a -> Quantum a
fromList = Quantum
量子ビットの定義と生成(Code_5)
-- ============================================================
-- 量子ビット
-- ============================================================
-- | 量子ビット: 0 または 1 の状態
data Qubit = Q0 | Q1 deriving (Eq, Ord, Show)
-- | |0⟩ 状態
qubit0 :: Quantum Qubit
qubit0 = pure Q0
-- | |1⟩ 状態
qubit1 :: Quantum Qubit
qubit1 = pure Q1
-- | |+⟩ 状態 = (|0⟩ + |1⟩) / √2
qplus :: Quantum Qubit
qplus = Quantum [(h, Q0), (h, Q1)]
where h = (1 / sqrt 2) :+ 0
-- | |-⟩ 状態 = (|0⟩ - |1⟩) / √2
qminus :: Quantum Qubit
qminus = Quantum [(h, Q0), (negate h, Q1)]
where h = (1 / sqrt 2) :+ 0
量子ゲートの定義(Code_6)
-- ============================================================
-- 量子ゲート
-- ============================================================
-- | アダマールゲート (Hadamard gate)
--
-- 重ね合わせを作る最も基本的なゲート。
--
-- * H|0⟩ = |+⟩ = (|0⟩ + |1⟩) / √2
-- * H|1⟩ = |-⟩ = (|0⟩ - |1⟩) / √2
--
-- >>> do { q <- qubit0; hadamard q }
-- 50%/50% の重ね合わせ
hadamard :: Qubit -> Quantum Qubit
hadamard Q0 = qplus
hadamard Q1 = qminus
-- | パウリXゲート (NOT gate)
--
-- 量子ビットを反転させる。
--
-- * X|0⟩ = |1⟩
-- * X|1⟩ = |0⟩
pauliX :: Qubit -> Quantum Qubit
pauliX Q0 = pure Q1
pauliX Q1 = pure Q0
-- | パウリZゲート
--
-- |1⟩ に位相 -1 を与える。
--
-- * Z|0⟩ = |0⟩
-- * Z|1⟩ = -|1⟩
pauliZ :: Qubit -> Quantum Qubit
pauliZ Q0 = pure Q0
pauliZ Q1 = Quantum [((-1) :+ 0, Q1)]
-- | Sゲート(位相ゲート)
--
-- |1⟩ に位相 i (π/2) を与える。
-- S² = Z となる。
--
-- * S|0⟩ = |0⟩
-- * S|1⟩ = i|1⟩
sGate :: Qubit -> Quantum Qubit
sGate Q0 = pure Q0
sGate Q1 = Quantum [(0 :+ 1, Q1)]
-- | Tゲート(π/8ゲート)
--
-- |1⟩ に位相 e^(iπ/4) を与える。
-- T² = S となる。
--
-- * T|0⟩ = |0⟩
-- * T|1⟩ = e^(iπ/4)|1⟩
tGate :: Qubit -> Quantum Qubit
tGate Q0 = pure Q0
tGate Q1 = Quantum [(cos (pi/4) :+ sin (pi/4), Q1)]
-- | 制御NOTゲート (CNOT)
--
-- 2量子ビットゲート。制御ビットが|1⟩のときだけ
-- ターゲットビットを反転する。
-- 量子もつれを作るのに使う。
--
-- * CNOT|00⟩ = |00⟩
-- * CNOT|01⟩ = |01⟩
-- * CNOT|10⟩ = |11⟩
-- * CNOT|11⟩ = |10⟩
cnot :: Qubit -> Qubit -> Quantum (Qubit, Qubit)
cnot ctrl tgt = case ctrl of
Q0 -> pure (Q0, tgt) -- 制御が0なら何もしない
Q1 -> do -- 制御が1ならターゲットを反転
tgt' <- pauliX tgt
pure (Q1, tgt')
測定・観測(Code_7)
-- ============================================================
-- 測定と観測
-- ============================================================
-- | 同じ状態の振幅をまとめて正規化
normalize :: (Ord a) => Quantum a -> Quantum a
normalize (Quantum states) = Quantum normalized
where
-- 同じ状態の振幅を足し合わせる
grouped = groupBy ((==) `on` snd) $ sortBy (compare `on` snd) states
summed = [ (sum (map fst g), x) | g@((_, x):_) <- grouped ]
-- ほぼゼロの振幅を除去
normalized = [ (a, x) | (a, x) <- summed, magnitude a > 1e-10 ]
-- | 各状態の測定確率を計算
--
-- 確率 = |振幅|²
probabilities :: (Ord a) => Quantum a -> [(Double, a)]
probabilities q =
[ (magnitude amp ^ (2 :: Int), x)
| (amp, x) <- runQuantum (normalize q)
]
-- | 内部の振幅リストを取得
amplitudes :: Quantum a -> WaveFunction a
amplitudes = runQuantum
-- | 確率的に1つの状態を測定(IO版)
--
-- 量子力学の測定をシミュレート。
-- 各状態は振幅の2乗に比例した確率で選ばれる。
measure :: (Ord a) => Quantum a -> IO a
measure q = do
r <- randomRIO (0.0, 1.0)
let probs = probabilities q
pure $ pick r probs
where
pick _ [(_, x)] = x
pick r ((p, x):rest)
| r < p = x
| otherwise = pick (r - p) rest
pick _ [] = error "empty quantum state"
-- | 確率分布を純粋に観測(測定せずに確率を見る)
observe :: (Ord a, Show a) => Quantum a -> String
observe q = unlines
[ show x ++ ": " ++ showPercent p
| (p, x) <- probabilities q
]
where
showPercent p = show (round (p * 100) :: Int) ++ "%"
ユーティリティ(Code_8)
-- | デバッグ用の表示
showQuantum :: (Show a) => Quantum a -> String
showQuantum (Quantum states) = unlines
[ showAmp amp ++ " |" ++ show x ++ "⟩"
| (amp, x) <- states
]
where
showAmp (r :+ i)
| abs i < 1e-10 = showReal r
| abs r < 1e-10 = showImag i ++ "i"
| i >= 0 = showReal r ++ "+" ++ showImag i ++ "i"
| otherwise = showReal r ++ showImag i ++ "i"
showReal x = show (fromIntegral (round (x * 1000)) / 1000 :: Double)
showImag x = show (fromIntegral (round (x * 1000)) / 1000 :: Double)
さて,ここまでのプログラムをファイルOpenQL/Quantum.hsに記述しておきます.このQuantumモジュールの一部の機能を使った例(サンプルプログラム)は次のように書けます.
サンプルプログラム
module Main where
import OpenQL.Quantum
-- ============================================================
-- 使用例
-- ============================================================
-- | 例1: 単純な重ね合わせ
--
-- do記法で量子計算を記述
example1 :: Quantum Qubit
example1 = do
q <- qubit0 -- |0⟩ から開始
hadamard q -- アダマールゲートで重ね合わせに
-- | 例2: ベル状態(量子もつれ)
--
-- 2つの量子ビットが「もつれた」状態を作る。
-- 測定すると、必ず同じ値(00か11)になる。
bellState :: Quantum (Qubit, Qubit)
bellState = do
q1 <- qubit0 -- 第1ビット: |0⟩
q2 <- qubit0 -- 第2ビット: |0⟩
q1' <- hadamard q1 -- 第1ビットを重ね合わせに
cnot q1' q2 -- CNOTで量子もつれを作成
-- | 例3: 量子干渉
--
-- アダマールゲートを2回適用すると元に戻る。
-- これは振幅の足し合わせ(干渉)による。
--
-- H(H|0⟩) = H(|+⟩) = |0⟩
interference :: Quantum Qubit
interference = do
q <- qubit0
q' <- hadamard q -- |0⟩ → |+⟩
hadamard q' -- |+⟩ → |0⟩ (振幅が干渉して戻る)
-- | 例4: 量子コイン投げ
--
-- 純粋な乱数生成
quantumCoinFlip :: Quantum Bool
quantumCoinFlip = do
q <- qubit0
q' <- hadamard q
pure (q' == Q1) -- Q1 なら True
-- | 例5: 3量子ビットのGHZ状態
--
-- |GHZ⟩ = (|000⟩ + |111⟩) / √2
ghzState :: Quantum (Qubit, Qubit, Qubit)
ghzState = do
q1 <- qubit0
q2 <- qubit0
q3 <- qubit0
q1' <- hadamard q1
(q1'', q2') <- cnot q1' q2
(_, q3') <- cnot q1'' q3
pure (q1'', q2', q3')
-- ============================================================
-- メイン
-- ============================================================
main :: IO ()
main = do
putStrLn "╔══════════════════════════════════════════════╗"
putStrLn "║ 量子計算モナド - do記法による量子プログラミング ║"
putStrLn "╚══════════════════════════════════════════════╝"
putStrLn "\n【例1】アダマールゲート(重ね合わせ)"
putStrLn "コード: do { q <- qubit0; hadamard q }"
putStrLn "結果:"
putStr $ observe example1
putStrLn "\n【例2】ベル状態(量子もつれ)"
putStrLn "コード: do { q1 <- qubit0; q2 <- qubit0"
putStrLn " q1' <- hadamard q1"
putStrLn " cnot q1' q2 }"
putStrLn "結果:"
putStr $ observe bellState
putStrLn "\n【例3】量子干渉 (H → H = I)"
putStrLn "コード: do { q <- qubit0; q' <- hadamard q; hadamard q' }"
putStrLn "結果:"
putStr $ observe interference
putStrLn "\n【例4】量子コイン投げ(測定シミュレーション)"
putStrLn "10回測定してみる..."
results <- sequence $ replicate 10 (measure quantumCoinFlip)
putStrLn $ "結果: " ++ show results
putStrLn "\n【例5】GHZ状態(3量子ビットもつれ)"
putStrLn "結果:"
putStr $ observe ghzState
このサンプルを実行するには,次のようにします.内部でRandomIOを呼び出していますので,-package=randomを指定してください.
$ runghc -package=random Main.hs
その実行結果は次のとおりです.
サンプルプログラムの実行結果
╔══════════════════════════════════════════════╗
║ 量子計算モナド - do記法による量子プログラミング ║
╚══════════════════════════════════════════════╝
【例1】アダマールゲート(重ね合わせ)
コード: do { q <- qubit0; hadamard q }
結果:
Q0: 50%
Q1: 50%
【例2】ベル状態(量子もつれ)
コード: do { q1 <- qubit0; q2 <- qubit0
q1' <- hadamard q1
cnot q1' q2 }
結果:
(Q0,Q0): 50%
(Q1,Q1): 50%
【例3】量子干渉 (H → H = I)
コード: do { q <- qubit0; q' <- hadamard q; hadamard q' }
結果:
Q0: 100%
【例4】量子コイン投げ(測定シミュレーション)
10回測定してみる...
結果: [True,False,False,False,False,False,False,False,True,True]
【例5】GHZ状態(3量子ビットもつれ)
結果:
(Q0,Q0,Q0): 50%
(Q1,Q1,Q1): 50%
Wavefunctionの定義など内部はもう少し良い実装があるかもしれませんが,いずれにしてもこのような実装でも量子計算をモナドで扱えるようになっているのが感じていただけると嬉しいです.
おわりに
いかがでしたでしょうか.本稿では,量子計算をモナドとして扱えるようにして,Haskellのdo記法を使って量子モナドの部分と純粋な計算の部分を分けて実装できるような例を提示しました.量子計算のプログラミング事情は,python での実装が主だっています.しかし,言語仕様としては Haskell との相性がよいように
は考えております.OSSコミュニティ活動2では.python 以外のプログラミング言語への量子計算の適合を目指してまいります.応援いただけると嬉しいです!!
ここまで,ご一読いただきまして有り難うございます.
最後に,本稿を記載するために検証したHaskell環境を記しておきます.お手元の環境で検証する際に,動作が異なるときには参考になるかもしれません.
本稿の環境
本稿のために使用した環境は以下となります。
macOS: Sequoia 15.6.1 (chip: Apple M1)
GHCup: 0.1.50.2
GHC: 9.12.2(9.6.7も確認済み)
(●)(●) Happy Hacking!
/"" __""\
-
@kyamaz は、オープンソース・コミュニティ『OpenQL』プロジェクトを通じて、皆さんと共に量子情報・量子コンピューティングの分野で挑戦しております。引き続きどうぞ宜しくお願い致します。 ↩
-
OpenQLプロジェクトは、量子コンピューターを扱うためのライブラリを開発するためのオープンソースプロジェクトです。量子情報、量子コンピューターに興味のある人たちが集うコミュニティを運営しております。詳しくはconnpassのサイトをご覧ください。 ↩