計算コストの測定
次に、計算コストを測定するための数学的な枠組みを説明します。本コースの目的に絞った内容となっています。 アルゴリズムの解析と計算複雑性はそれぞれ独立した分野であり、これらの概念についてさらに多くのことを述べています。
出発点として、前のレッスンで登場した以下の図を考えてみましょう。これは計算の非常に高レベルな抽象的表現です。
計算そのものは、Pythonで書かれたコンピュータプログラム、チューリングマシン、ブール回路、量子回路など、さまざまな方法でモデル化・記述できます。 本コースでは(ブール回路と量子回路の両方を含む)回路に焦点を当てます。
エンコードと入力長
まず、計算問題の入力と出力について考えます。ここでは入力と出力が二進文字列であると仮定します。 他の記号を使うこともできますが、この説明では二進文字列の入出力に限定してシンプルに保ちます。 二進文字列を通じて、解きたい問題に関係する様々な興味深いオブジェクト(数値、ベクトル、行列、グラフ、あるいはそれらのリストなど)をエンコードすることができます。
たとえば、非負 整数をエンコードするには二進表記を使うことができます。 以下の表は、最初の9つの非負整数の二進エンコードと、各エンコードの長さ(つまりビットの総数)を示しています。
| 数値 | 二進エンコード | 長さ |
|---|---|---|
| 0 | 0 | 1 |
| 1 | 1 | 1 |
| 2 | 10 | 2 |
| 3 | 11 | 2 |
| 4 | 100 | 3 |
| 5 | 101 | 3 |
| 6 | 110 | 3 |
| 7 | 111 | 3 |
| 8 | 1000 | 4 |
必要に応じて符号ビットを付加することで、正の整数だけでなく負の整数も扱えるようにこのエンコードを簡単に拡張できます。 また、非負整数の二進表現に先頭のゼロを許すことが便利な場合もあります。先頭ゼロはエンコードされた値を変えませんが、固定サイズの文字列やワードを埋めるために利用できます。
非負整数を表すために二進表記を使うのは一般的かつ効率的ですが、好みに応じて以下の表のような 別の方法で二進文字列を使って非負整数を表すこともできます。 これらの代替手段の詳細はここでは重要ではありません。ポイントは、使用するエンコード方式に選択の余地があるということを明確にすることだけです。
| 数値 | 単項エンコード | 辞書式エンコード |
|---|---|---|
| 0 | ||
| 1 | 0 | 0 |
| 2 | 00 | 1 |
| 3 | 000 | 00 |
| 4 | 0000 | 01 |
| 5 | 00000 | 10 |
| 6 | 000000 | 11 |
| 7 | 0000000 | 000 |
| 8 | 00000000 | 001 |
(この表において、記号 は空文字列を表します。空文字列は記号を含まず、長さはゼロです。当然ながら、わかりにくさを避けるために、何も書かないのではなく のような特別な記号を使って空文字列を表します。)
ベクトルや行列、あるいは分子の記述のようなより複雑なオブジェクトなど、他の種類の入力も二進文字列としてエンコードできます。 非負整数の場合と同様に、さまざまなエンコード方式を選択したり考案したりできます。 与えられた問題への入力をエンコードするためにどのような方式を採用しても、入力文字列の長さは解かれる問題インスタンスの規模を表すものとして解釈されます。
たとえば、非負整数 を二進表記で表すために必要なビット数は、 と表記されることがあり、以下の式で与えられます。
整数因数分解問題の入力を二進表記でエンコードすると仮定すると、数値 の入力長は になります。 特に注目すべき点は、入力 の長さ(または規模)は そのものではないということです。 が大きい場合でも、二進表記で を表すためにこれほど多くのビットは必要ありません。
厳密に形式的な観点からは、計算問題やタスクを考える際には必ず、入力として与えられたり出力として生成されたりするオブジェクトをエンコードするための具体的な方式が選択されていると理解する必要があります。 これにより、興味深い問題を解く計算を、二進文字列入力から二進文字列出力への変換として抽象的に捉えることができます。
オブジェクトを二進文字列としてエンコードする方法の詳細は、ある程度はこれらの計算において重要です。 しかし通常、計算コストを分析する際にはこれらの詳細についてあまり気にしません。これは、二次的に重要な詳細への深入りを避けるためです。 基本的な考え方は、「合理的な」エンコード方式間で変換する計算コストは、実際の問題を解くコストに比べてわずかであると期待されるというものです。 そうでない場合には、詳細を(必要に応じて)明確にすることができます。
たとえば、非常に単純な計算で、非負整数の二進表現とその辞書式エンコード間の変換(詳細は説明していませんが、上の表から推測できます)ができます。このため、整数因数分解の計算コストは、入力 にどちらのエンコードを使うかを切り替えても大きく変わりません。 一方、単項表記で非負整数をエンコードすると、必要な記号の総数が指数関数的に膨れ上がります。そのため、これは「合理的な」エンコード方式とは見なされません。
基本演算
次に、上の図で青い長方形として表された計算そのものを考えましょう。
計算コストを測定する方法として、各計算が必要とする基本演算の数を数えます。
直感的には、基本演算とは少数の固定されたビットまたは量子ビットを扱い、ANDの演算のように素早く簡単に実行できる演算のことです。
一方、factorint 関数を実行することは基本演算とは合理的に見なせません。
形式的には、基本演算として何を採用するかは使用する計算モデルによって異なります。 ここでは回路モデル、特に量子回路とブール回路に焦点を当てます。
万能ゲートセット
回路ベースの計算モデル では、通常、各ゲートが基本演算とみなされます。 これにより、回路に使用するゲートを正確に何にするかという問題が生じます。 まず量子回路に焦点を当てると、このシリーズでこれまで ゲート、スワップゲート、制御付きゲートの各種(制御NOT、Toffoli、Fredkin ゲートを含む)、および標準基底測定を表すゲートを見てきました。 CHSHゲームの文脈では、いくつかの追加の回転ゲートも登場しました。
クエリモデルの文脈でクエリゲートも議論しましたが、任意の数の量子ビットに作用する任意のユニタリ演算 もゲー トとして見なせることも示しました。しかし、この議論ではこれらの両方の選択肢を除外します。 クエリモデルでは作業しません(ただし、基本演算を使ったクエリゲートの実装については、レッスンの後半で説明します)。また、潜在的に数百万の量子ビットに作用する任意のユニタリゲートを基本演算とみなすことは、意味のある現実的な計算コストの概念にはつながりません。
少数の量子ビットに作用する量子ゲートに限定した場合、どれを基本演算とするかを決める一つのアプローチは厳密な基準を定めることですが、それは標準的なアプローチでも本コースで採用するアプローチでもありません。 むしろ、単純に選択します。
以下は量子回路のデフォルトゲートセットとして採用する標準的な選択肢です:
- 以下のリストからの1量子ビットユニタリゲート: および
- 制御NOTゲート
- 1量子ビット標準基底測定
一般的な代替案として、Toffoli、Hadamard、 ゲートを基本演算として(標準基底測定に加えて)扱うことがあります。 すべての1量子ビットゲートを基本演算と見なす場合もありますが、ゲートの実行精度を適切に考慮しないと非現実的に強力なモデルになってしまいます。
デフォルトコレクションのユニタリゲートは万能ゲートセットを形成します。 これは、これらのゲートだけで構成される回路を使って、任意の数の量子ビットに対する任意のユニタリ演算を任意の精度で近似できることを意味します。 明確にしておくと、万能性の定義はそのような近似のコスト、つまり必要なゲートの数については何も要求しません。 実際、数学的測度の概念に基づく比較的単純な議論から、ほとんどのユニタリ演算は非常に高いコストを持たなければならないことが明らかになります。 量子ゲートセットの万能性を証明することは単純ではなく、このコースでは扱いません。
ブール回路では、AND、OR、NOT、FANOUTゲートを基本演算として採用します。 実際にはANDゲートとORゲートの両方は必要ありません。すべての入出力線にNOTゲートを配置することでド・モルガンの法則を使ってどちらか一方から他方に変換できます。しかし、両方を許可することは典型的であり便利です。 AND、OR、NOT、FANOUTゲートは決定論的計算に対して万能なセットを形成します。つまり、固定数の入力ビットから固定数の出力ビ ットへの任意の関数をこれらのゲートで実装できます。
測定の遅延原理
標準基底測定ゲートは量子回路の途中に現れることがありますが、最後まで遅延させると便利な場合があります。 これにより、量子計算を、(計算そのものを表す)ユニタリ部分と、その後の量子ビットを測定して結果を出力する単純な読み出しフェーズとして捉えることができます。 これは常に可能であり、ただし各標準基底測定に対して追加の量子ビットを1つ加える必要があります。 以下の図では、右の回路が左のゲートに対してこれをどのように行うかを示しています。
具体的には、左の回路の古典ビットが右では量子ビット( 状態に初期化)に置き換えられ、標準基底測定が制御NOTゲートに置き換えられ、その後に下の量子ビットの標準基底測定が続きます。 ポイントは、右側の回路の標準基底測定を回路の最後まで押しやれることです。 左の回路の古典ビットが後で制御ビットとして使われる場合、右の回路の下の量子ビットを代わりに制御として使えば、全体的な効果は同じになります。 (左の回路の古典ビットが測定後に別の測定によって上書きされないと仮定しています。もし上書きされる場合は、以前の測定で使われたビットを上書きする代わりに新しい古典ビットを使えばよいだけです。)
回路サイズと深度
回路サイズ
回路内のゲートの総数は、その回路のサイズと呼ばれます。 したがって、回路内のゲートが基本演算を表すと仮定すれば、回路のサイズは必要な基本演算の数、つまり計算コストを表します。 与えられた回路 のサイズを と書きます。
たとえば、2つのビットのXORを計算する以下のブール回路を考えましょう。
この回路のサイズは7です。合計7つのゲートがあるからです。 (FANOUTゲートはゲートとして数えないことが多いですが、このレッスンではゲートとして数えます。)
時間、コスト、回路深度
時間は計算において極めて重要なリソース、または制限的な制約です。
RSA1024の因数分解のタスクなど、上記の例はこの観点を強調しています。
factorint 関数がRSA1024を因数分解できないわけではありません。単に、それが終わるのを待つ時間が十分にないだけです。
計算コストという概念は、計算を実行するために必要な基本演算の数として、計算の実装に必要な時間の抽象的な代替指標として意図されています。 各基本演算は実行するのに一定量の時間を必要とし、より多くの基本演算が必要な計算は少なくとも一般的により長くかかります。 シンプルさを保つために、計算コストとアルゴリズムの実行時間の間のこの関連付けを続けます。
しかし、回路のサイズは実行にかかる時間と必ずしも直接対応するわけではないことに注意してください。 たとえば、2つのビットのXORを計算するブール回路では、2つのFANOUTゲートを同時に実行でき、2つのNOTゲートも同様、2つのANDゲートも同様です。 この並列化の可能性を考慮した、回路の効率を測る別の方法が深度です。 これは回路内で必要なゲートの層の最小数で あり、各層内のゲートは異なる線上で動作します。 同等に、回路の深度は入力線から出力線への任意のパス上で出会うゲートの最大数です。 たとえば上記の回路では、深度は4です。
回路深度は並列計算の実行時間を形式化する一つの方法です。 これは高度なトピックであり、特定の計算に必要な深度を最小化するための非常に洗練された回路構成が知られています。 また、回路深度に関する非常に興味深い未解決の問題も存在します。 (たとえば、GCDの計算に必要な回路深度については多くが不明のままです。) このシリーズでは回路深度についてこれ以上多くは述べませんが、随時いくつかの興味深い事実を紹介します。並列化が計算上の優位性の潜在的な源であることは明確に認識されるべきです。
ゲートへの異なるコストの割り当て
回路サイズと計算コストに関する最後の注意として、すべてのゲートを同等に扱うのではなく、ゲートに異なるコストを割り当てることが可能です。
たとえば、すでに述べたように、FANOUTゲートはブール回路では無料とみなされることが多く、つまりFANOUTゲートのコストをゼロとすることができます。 別の例として、クエリモデルで作業しており、回路がブラックボックス形式の入力関数に対して行うクエリ数を数える場合、クエリゲートに単位コストを割り当て、Hadamardゲートなど他のゲートにはゼロ コストを割り当てていることになります。 最後の例として、考慮するハードウェアに応じて実装の難しさによってゲートに異なるコストを割り当てることがあります。
これらのオプションはすべて異なる文脈で合理的ですが、このレッスンではシンプルに保ち、計算コストの表現として回路サイズを使います。
入力長の関数としてのコスト
私たちが主に関心を持つのは、入力が大きくなるにつれて計算コストがどのようにスケールするかです。 これにより、アルゴリズムのコストを入力長の関数として表すことになります。
回路のファミリー
与えられた計算問題への入力は長さが異なり、任意に大きくなりえます。 一方、すべての回路は固定数のゲートと線を持ちます。 このため、アルゴリズムを回路の観点から考える場合、アルゴリズムを表現するために無限に大きな回路のファミリーが一般的に必要になります。 回路のファミリーとは、サイズが大きくなりより大きな入力に対応できる回路の列のことです。
たとえば、factorint が使用するような古典的な整数因数分解アルゴリズムがあると想像してください。
すべての古典的 アルゴリズムと同様、このアルゴリズムはブール回路を使って実装できますが、そのためには入力長ごとに別々の回路が必要です。
異なる入力長の回路を見ると、入力長が大きくなるにつれてそのサイズが自然に大きくなるのがわかります。これは、4ビット整数の因数分解が1024ビット整数の因数分解よりはるかに簡単で、基本演算の数もはるかに少ないという事実を反映しています。
これにより、アルゴリズムの計算コストを関数 で表すことになります。 は ビット入力に対してアルゴリズムを実装する回路のゲート数と して定義されます。 より形式的には、ブール回路モデルのアルゴリズムは回路の列 で記述されます。ここで は ビット入力(またはより一般的には、何らかの形で によってパラメータ化された規模の入力)に対して対象の問題を解き、このアルゴリズムのコストを表す関数 は次のように定義されます。
量子回路でも状況は同様で、より長い入力文字列に対応するためにより大きな回路が必要になります。
例:整数の加算
さらに説明するために、整数の加算問題を考えてみましょう。これは整数の因数分解やGCDの計算よりもはるかに単純です。
この問題を解くためのブール回路をどのように設計すればよいでしょうか?
シンプルにするために、 と がともに二進表記の ビット文字列で表された非負整数の場合に限定しましょう。 これらのエンコードでは任意の数の先頭ゼロを許します。したがって
出力は結果を表す ビットの二進文字列になります。これは結果を表すのに必要な最大ビット数です。
二進表現の標準的な加算アルゴリズムから始めます。これは世界中の小学校で教えられる加算方法の2進数版です。 このアルゴリズムは次のようにブール回路で実装 できます。
最下位ビットから始め、それらのXORを計算して和の最下位ビットを求めます。 次に、 と の2つの最下位ビットのANDであるキャリービットを計算します。 これら2つの演算をまとめて半加算器と呼ぶことがあります。
これまで何度か見たXOR回路にANDゲートと2つのFANOUTゲートを組み合わせて、10ゲートで半加算器を作ることができます。 もし何らかの理由でXORゲートを基本演算のセットに含めることにした場合、半加算器を作るには1つのANDゲート、1つのXORゲート、2つのFANOUTゲートが必要です。
より上位のビットに進むと、同様の手順を使いますが、今度は前の位からのキャリービットを計算に含めます。 2つの半加算器をカスケードし、それらが生成するキャリービットのORをとることで、全加算器として知られるものを作ることが できます。
これには合計21ゲートが必要です:2つのANDゲート、2つのXORゲート(それぞれ7ゲートで実装)、1つのORゲート、4つのFANOUTゲートです。
最後に、全加算器をカスケードすることで、非負整数の加算のためのブール回路が得られます。たとえば、以下の回路は2つの4ビット整数の和を計算します。
一般に、これには
個のゲートが必要です。 XORゲートを基本演算のセットに含めることにした場合、 個のANDゲート、 個のXORゲート、 個のORゲート、 個のFANOUTゲートが必要となり、合計で 個のゲートになります。 さらにFANOUTゲートを数えない場合は 個のゲートになります。
漸近的記法
一方では、上記の整数加算の例のように、様々な計算に必要なゲート数を正確に知ることは重要です。 これらの詳細は実際に回路を構築するために必要です。
他方、加算よりはるかに複雑なタスクを含む関心のあるすべての計算でこのレベルの詳細な分析を行うと、すぐに詳細に埋もれてしまいます。 管理しやすくするため、また二次的に重要な詳細を意図的に省略するために、アルゴリズムを分析する際には通常ビッグO記法を使います。 この記法を通じて、関数のオーダー(増加の程度)を表現できます。
形式的には、2つの関数 と がある場合、正の実数 と正の整数 が存在して
がすべての に対して成り立つとき、 と書きます。 通常、 はできるだけ単純な式を選びます。これにより、関数の漸近的な挙動を簡単な言葉で表現できます。 たとえば、 です。
この記法は複数の引数を持つ関数にも比較的単純な方法で拡張できます。 たとえば、正の整数 と 上で定義された2つの関数 と がある場合、正の実数 と正の整数 が存在して
が のときに成り立つなら、 と書きます。
この記法を非負整数の加算の例に結びつけると、ブール回路のファミリー (ここで は2つの ビット非負整数を足し合わせる)が存在して となると結論付けられます。 これは加算のコストが入力サイズとともにどのようにスケールするかの最も本質的な特徴を明らかにします:線形にスケールします。
また、XORゲートのコストを単位コストとするか とするかという具体的な詳細に依存しないことも注目してください。 一般に、ビッグO記法を使うことで、そのような低レベルの詳細に左右されない計算コストの記述が可能になります。
さらなる例
計算数論からのいくつかの問題の例を挙げます。まず乗算から始めます。
この問題のブール回路の作成は加算の回路よりも難しいですが、標準的な乗算アルゴリズムを考えることで、この問題に対してサイズ の回路を思いつくことができます( と がともに ビットの二進表現であると仮定)。 より一般的には、 が ビットで が ビットの場合、 と の乗算のためにサイズ のブール回路があります。
実際には、よりよいスケーリングを持つ他の乗算方法もあります。 たとえば、Schönhage-Strassenの乗算アルゴリズムを使うと、2つの ビット整数の乗算に対してコスト のブール回路を作ることができます。 ただし、この方法の複雑さにより大きなオーバーヘッドが生じるため、数万ビットを持つ数値に対してのみ実用的です。
別の基本的な問題は除算で、整数の除数と被除数が与えられたときに商と余りの両方を計算することを意味します。
整数除算のコストは乗算と同様です: が ビットで が ビットの場合、この問題を解くためにサイズ のブール回路があります。 そして乗算と同様に、漸近的により優れた方法が知られています。
次に、GCDを計算する既知のアルゴリズムを加算と乗算のものと比較できます。 ビット数 と ビット数 のGCDを計算するユークリッドのアルゴリズムには、サイズ のブール回路が必要であり、これは乗算と除算の標準アルゴリズムと同様です。 また、乗算と除算と同様に、漸近的に高速なGCDアルゴリズムがあります。2つの ビット数のGCDを計算するのに 個の基本演算しか必要としないものも含まれます。
数論で出てくるやや費用のかかる計算としてモジュラー冪乗があります。
とは、 を で割った余り、つまり かつある整数 に対して を満たす唯一の整数 を意味し ます。
が ビット、 が ビット、 が ビットの場合、この問題はサイズ のブール回路で解くことができます。 これはまったく明らかではありません。 解決策は を最初に計算してから余りをとることではありません。そうすると を格納するだけで指数関数的なビット数が必要になります。 むしろ、 の二進表現を使って計算全体を でのモジュラー演算として実行する冪乗アルゴリズム(二進法または繰り返し二乗法とも呼ばれる)を使うことができます。 がすべて ビット数だと仮定すると、 アルゴリズム(3次時間アルゴリズム)が得られます。 そして再び、より複雑だが漸近的により高速なアルゴリズムが知られています。