変数と式の定義

ヘッダファイルと名前空間

Hi-QUBOを使用するには,ヘッダファイル qbpp/qbpp.hpp をインクルードし,qbpp 名前空間を使用します.

変数と式の定義

変数は qbpp::var("name") を auto 型推論とともに使用して定義できます. 指定した name は,変数を std::cout で出力する際に使用されます.

式は +,-,* などの標準的な算術演算子を使って構築します.

以下のサンプルプログラムでは,3つの変数 a,b,c と式 f を定義し,std::cout を使って出力しています.

variables-and-expressions-program1.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto a = qbpp::var("a");
  auto b = qbpp::var("b");
  auto c = qbpp::var("c");
  auto f = (a + b - 1) * (b + c - 1);
  std::cout << "f = " << f << std::endl;
}

式 (a + b - 1) * (b + c - 1) は自動的に展開され,f に格納されます.

このHi-QUBOプログラムでは,変数 a,b,c は qbpp::Var クラスのオブジェクトであり,式 f は qbpp::Expr クラスのオブジェクトです.

ヘッダとライブラリのパスが適切に設定されていれば,このプログラム(test.cpp として保存)は g++ で以下のようにコンパイルできます.

g++ test.cpp -o test -std=c++17 -ldl -pthread

実行すると,展開された式が出力されます.

./test
f = 1 +a*b +b*b +a*c +b*c -a -b -b -c

注意 qbpp::var() の変数名は省略可能です. 省略した場合,{0},{1} などのデフォルト名が自動的に割り当てられます.

WARNING qbpp::Expr をはじめとするHi-QUBOのほとんどのクラスインスタンスは,std::cout を使ってテキストとして出力できます. ただし,このテキスト出力は安定性が保証されておらず,フォーマットが将来のリリースで変更される可能性があるため,後続の計算の入力として使用すべきではありません. また,Hi-QUBOドキュメントに示されている出力は古いバージョンで生成されたものである可能性があり,最新バージョンの出力とは異なる場合があります.

式の簡約化

qbpp::Expr オブジェクトに格納された式は,simplify() メンバ関数を呼び出すことで簡約化できます.

  std::cout << "f = " << f.simplify() << std::endl;

この変更により,プログラムの出力は以下のようになります.

f = 1 -a -2*b -c +a*b +a*c +b*b +b*c

メンバ関数 f.simplify() は式 f を簡約化し,結果の値を返します.その値が std::cout によって出力されます.

すべての変数が 2値(0または1) をとると仮定すると,恒等式 \(b^2=b\) を使って式をさらに簡約化できます. この目的には,代わりに simplify_as_binary() を使用します.

  std::cout << "f = " << f.simplify_as_binary() << std::endl;

出力は以下のようになります.

f = 1 -a -b -c +a*b +a*c +b*c

簡約化関数は,各項内の変数と式内の項を並べ替え,低次の項が先に表示されるようにし,同じ次数の項は変数の辞書順でソートします. 変数自体は定義された順序で並べられます.

スピン変数による式の簡約化

変数が スピン値 \(-1\)/\(+1\) をとると仮定する場合,恒等式 \(b^2 = 1\) を使って式をさらに簡約化できます. この場合,simplify_as_spin() メンバ関数を使用して式を簡約化します.

  std::cout << "f = " << f.simplify_as_spin() << std::endl;

出力は以下のようになります.

f = 2 -a -2*b -c +a*b +a*c +b*c

簡約化のためのグローバル関数

メンバ関数は f に格納された式を更新します. f を変更したくない場合は,代わりにグローバル関数 qbpp::simplify(f),qbpp::simplify_as_binary(f),qbpp::simplify_as_spin(f) を使用できます.これらは f を変更せずに簡約化された式を返します.

注意 Hi-QUBOでは,ほとんどの メンバ関数 はオブジェクトをその場で更新しますが,グローバル関数 は元のオブジェクトを変更せずに新しい値を返します.

否定リテラル

Hi-QUBOは ~ 演算子を使った 否定リテラル をネイティブにサポートしています. 2値変数 x に対して,式 ~x は \(1 - x\) を表します.

variables-and-expressions-program2.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto a = qbpp::var("a");
  auto b = qbpp::var("b");
  auto c = qbpp::var("c");
  auto d = qbpp::var("d");
  auto f = ~a * ~b * ~c * ~d + a * b;
  std::cout << "f = " << f << std::endl;
  std::cout << "f = " << qbpp::simplify_as_binary(f) << std::endl;
}

出力:

f = ~a*~b*~c*~d +a*b
f = a*b +~a*~b*~c*~d

否定リテラル ~x は,1 - x として展開されるのではなく,否定フラグを持つ単一の変数として内部的に格納されます. これはパフォーマンスにとって重要です.~x を単純に展開すると,~x1 * ~x2 * ... * ~xk のような \(k\) 個の否定リテラルの積は,\((1-x_1)(1-x_2)\cdots(1-x_k)\) を展開した後に最大 \(2^k\) 個の項を生成します. 例えば,上記の項 ~a*~b*~c*~d は単一の4次項として格納されますが,展開形 \((1-a)(1-b)(1-c)(1-d)\) は16個の項を生成します.

1 -a -b -c -d +a*b +a*c +a*d +b*c +b*d +c*d -a*b*c -a*b*d -a*c*d -b*c*d +a*b*c*d

Hi-QUBOに同梱されているすべてのソルバー(EasySolver,ExhaustiveSolver,ABS3 GPU Solver)は否定リテラルをネイティブに処理するため,求解前に展開する必要はありません.

PyQBPPのインストール

PyQBPPを使用するには,pipでインストールしてください:

pip install pyqbpp

ライブラリのインポート

PyQBPPを使用するには,**pyqbpp**モジュールをインポートします:

variables-and-expressions-program1.py
import pyqbpp as qbpp

変数と式の定義

変数は**qbpp.var("name")**を使って定義できます. 指定したnameは変数を表示する際に使用されます.

式は**+,-,***などの標準的な算術演算子を使って構築します.

以下のプログラムは,3つの変数a,b,cと式fを定義し,表示します:

variables-and-expressions-program2.py
import pyqbpp as qbpp

a = qbpp.var("a")
b = qbpp.var("b")
c = qbpp.var("c")
f = (a + b - 1) * (b + c - 1)
print("f =", f)

式(a + b - 1) * (b + c - 1)は自動的に展開され,fに格納されます.

このプログラムでは,a,b,cは変数であり,fは式です.

プログラムを実行すると,展開された式が表示されます:

f = 1 -b -c -a -b +a*b +a*c +b*b +b*c

注釈 qbpp.var()の変数名は省略可能です. 省略した場合,{% raw %}{0}{% endraw %},{% raw %}{1}{% endraw %},...のようなデフォルト名が自動的に割り当てられます.

注意 式をはじめとするPyQBPPのほとんどのオブジェクトは,print()を使ってテキストとして出力できます. ただし,このテキスト出力は安定性が保証されておらず,フォーマットが将来のリリースで変更される可能性があるため,後続の計算の入力として使用すべきではありません. また,PyQBPPドキュメントに示されている出力は古いバージョンで生成されたものである可能性があり,最新バージョンの出力とは異なる場合があります.

バイナリ変数の配列については 配列,多次元配列については 多次元変数,整数変数については 整数変数 を参照してください.

式の簡約化

fに格納された式は,**simplify()**メンバ関数を呼び出すことで簡約化できます:

print("f =", f.simplify())

この変更により,プログラムの出力は以下のようになります:

f = 1 -a -2*b -c +a*b +a*c +b*b +b*c

メンバ関数呼び出し**f.simplify()**は式fをその場で簡約化し,自身を返します.その値が出力されます.

バイナリ変数による式の簡約化

すべての変数が**バイナリ値(0または1)を取ると仮定すると,恒等式\(b^2=b\)を使って式をさらに簡約化できます. この目的には,代わりにsimplify_as_binary()**を使用します:

print("f =", f.simplify_as_binary())

すると出力は以下のようになります:

f = 1 -a -b -c +a*b +a*c +b*c

簡約化関数は,各項内の変数と式内の項を並べ替え,低次の項が先に表示されるようにし,同じ次数の項は変数の辞書順でソートします. 変数自体は定義された順序で並べられます.

スピン変数による式の簡約化

変数が**スピン値 \(-1\)/\(+1\)を取ると仮定する場合,恒等式\(b^2 = 1\)を使って式をさらに簡約化できます. この場合,simplify_as_spin()**メンバ関数を使って式を簡約化できます:

print("f =", f.simplify_as_spin())

すると出力は以下のようになります:

f = 2 -a -2*b -c +a*b +a*c +b*c

簡約化のためのグローバル関数

メンバ関数はfに格納された式を更新します. fを変更したくない場合は,代わりにグローバル関数**qbpp.simplify(f),qbpp.simplify_as_binary(f),qbpp.simplify_as_spin(f)**を使用できます.これらはfを変更せずに簡約化された式を返します.

variables-and-expressions-program3.py
import pyqbpp as qbpp
g = qbpp.simplify_as_binary(f)  # fは変更されず、gは新しい簡約化された式

注釈 PyQBPPでは,ほとんどの式オブジェクトに関するメンバ関数は処理結果でオブジェクト更新しますが,グローバル関数は元のオブジェクトを変更せずに新しい値を返します.

否定リテラル

PyQBPPは~演算子を使った否定リテラルをネイティブにサポートしています. バイナリ変数xに対して,式~xは\(1 - x\)を表します.

variables-and-expressions-program4.py
import pyqbpp as qbpp

a = qbpp.var("a")
b = qbpp.var("b")
c = qbpp.var("c")
d = qbpp.var("d")
f = ~a * ~b * ~c * ~d + a * b
print("f =", f)
print("f =", qbpp.simplify_as_binary(f))

出力:

f = ~a*~b*~c*~d +a*b
f = a*b +~a*~b*~c*~d

否定リテラル~xは,1 - xとして展開されるのではなく,否定フラグを持つ単一の変数として内部的に格納されます. これはパフォーマンスにとって重要です.~xを単純に展開すると,~x1 * ~x2 * ... * ~xkのような\(k\)個の否定リテラルの積は,\((1-x_1)(1-x_2)\cdots(1-x_k)\)を展開した後に最大\(2^k\)個の項を生成します. 例えば,上記の項~a*~b*~c*~dは単一の4次項として格納されますが,展開形\((1-a)(1-b)(1-c)(1-d)\)は16個の項を生成します:

1 -a -b -c -d +a*b +a*c +a*d +b*c +b*d +c*d -a*b*c -a*b*d -a*c*d -b*c*d +a*b*c*d

PyQBPPに同梱されているすべてのソルバー(EasySolver,ExhaustiveSolver,ABS3 GPU Solver)は否定リテラルをネイティブに処理するため,求解前に展開する必要はありません.