式の演算子と関数¶
以下の表は,qbpp::Exprオブジェクトで利用可能な演算子と関数をまとめたものです.
演算子/関数 |
演算子記号/関数名 |
関数タイプ |
戻り値の型 |
引数の型 |
|---|---|---|---|---|
型変換 |
|
グローバル |
|
|
代入 |
|
メンバ |
|
|
二項演算子 |
|
グローバル |
|
|
複合代入演算子 |
|
メンバ |
|
|
除算 |
|
グローバル |
|
|
複合除算 |
|
メンバ |
|
|
単項演算子 |
|
グローバル |
|
|
比較(等値) |
|
グローバル |
|
|
比較(範囲比較) |
|
グローバル |
|
|
比較(片側) |
|
グローバル |
|
|
二乗 |
|
グローバル |
|
|
二乗 |
|
メンバ |
|
- |
最大公約数 |
|
グローバル |
|
|
簡約化 |
|
グローバル |
|
|
簡約化 |
|
メンバ |
|
- |
評価 |
|
メンバ |
|
|
置換 |
|
グローバル |
|
|
置換 |
|
メンバ |
|
|
バイナリ/スピン変換 |
|
グローバル |
|
|
バイナリ/スピン変換 |
|
メンバ |
|
- |
HUBO→QUBO 変換 |
|
グローバル |
|
|
HUBO→QUBO 変換 |
|
メンバ |
|
- |
スライス(タプルインデックス) |
|
メンバ |
|
- |
連結 |
|
グローバル |
|
|
連結(スカラー付き) |
|
グローバル |
|
|
型変換: qbpp::toExpr()
グローバル関数**qbpp::toExpr()**は引数をqbpp::Exprインスタンスに変換して返します.
引数は以下のいずれかです:
整数
変数(
qbpp::Var)積項(
qbpp::Term)式(
qbpp::Expr)-- この場合,変換は行われません
これらの引数の型を総称してExprTypeと呼びます.
式関連の型: ExprType
**ExprType**という用語は,qbpp::Exprオブジェクトに変換可能な型のカテゴリを示します.
整数関連の型: IntとIntInf
Int: 通常の整数IntInf: 整数,-qbpp::inf,または+qbpp::infのいずれかで,無限の境界を表します.
グローバル関数とメンバ関数
qbpp::Exprに関連する演算子と関数は2つの形式で提供されます:
グローバル関数: 少なくとも1つのExprType引数を取り,通常は入力を変更せずに新しい
qbpp::Exprオブジェクトを返します.メンバ関数:
qbpp::Exprクラスのメンバ関数です. 多くの場合,呼び出し元のオブジェクトを更新し,結果のqbpp::Exprも返します.
例: sqr()
sqr()関数は式の二乗を計算し,両方の形式で利用できます:
sqr(f)(グローバル): fを変更せずにfの二乗を返しますf.sqr()(メンバ): fをその二乗に更新し,更新された式を返します
代入演算子: =
左辺はqbpp::Exprオブジェクトでなければなりません.
右辺はExprTypeでなければならず,まずqbpp::Exprに変換されます.
変換された式が左辺に代入されます.
二項演算子: +, -, *
これらの演算子はグローバル関数として定義されています.
2つのExprTypeオペランドを取り,結果を計算して返します.
少なくとも1つのオペランドがqbpp::Exprの場合,結果は常にqbpp::Exprになります.
どちらのオペランドもqbpp::Exprでない場合,結果はqbpp::Termになることがあります.
例
qbpp::Var型の変数xの場合:
2 + x:qbpp::Expr2 * x:qbpp::Term
複合代入演算子: +=, -=, *=
これらの演算子はメンバ関数として定義されています.
左辺はqbpp::Exprでなければなりません.
右辺のオペランドを使用して指定された演算が適用されます.
左辺の式がその場で更新されます.
除算/と複合除算/=
除算演算子/はグローバル関数として定義されています.
非整数のExprTypeオペランドを被除数として,整数オペランドを除数として取り,商をqbpp::Exprとして返します.
被除数の式は除数で割り切れなければなりません.つまり, 式内の整数定数項とすべての整数係数が除数で割り切れる必要があります.
複合除算演算子/=はメンバ関数として定義されています.
左辺は
qbpp::Exprでなければなりません.右辺は整数でなければなりません.
同じ割り切れ条件が適用され,除算はその場で実行され,左辺の式が更新されます.
比較(等値): ==
等値比較演算子==は以下を取ります:
左辺に非整数の
ExprType右辺に整数
等値制約が満たされたときに最小値0となる式を返します.
より具体的には,非整数のExprTypeオブジェクトfと整数nに対して,演算子はqbpp::sqr(f-n)を返します.
返されたオブジェクトgについて:
**
g**は制約式qbpp::sqr(f - n)を表し,**
g.body()**は基礎となる式fを返します.
制約式の qbpp::Expr
ここで g は制約式の qbpp::Expr(penalty + body のメタデータを持つ qbpp::Expr)です.
g.body() を呼ぶと,関連付けられた基礎となる qbpp::Expr オブジェクトが返されます.
比較(範囲比較): <= <=
範囲比較演算子は次の形式で記述されます:
l <= f <= u
ここで:
fは非整数のExprType,lとuは整数です.
この演算子は,範囲制約が満たされたときに最小値0となる式を返します.
より具体的には,単位間隔を持つ補助整数変数aが範囲[l,u-1]の値を取るように暗黙的に導入され,演算子は以下を返します:
(f - a)(f - (a + 1))
返された制約式 qbpp::Expr オブジェクトgについて:
**
g**は制約式(f - a)(f - (a + 1))を表し,**
g.body()**は基礎となる式fを返します.
片側比較: <=, >=
非整数 ExprType f と整数 n について,片側制約演算子が用意されています:
f <= n // f.neg_sum() <= f <= n と等価
f >= n // n <= f <= f.pos_sum() と等価
それぞれ片側制約が満たされたときに最小値 0 となる制約式を返します.
省略された側の境界は内部で f.neg_sum()(f の最小可能値)または f.pos_sum()(f の最大可能値)に設定されるので,補助整数変数の範囲は要素が取りうる範囲ぴったりに収まります.
単独片側演算子と qbpp::inf を用いたチェイン形は同一の制約式を生成します:
単独片側形 |
同等のチェイン形 |
|---|---|
|
|
|
|
単独片側形が推奨記法です.チェイン形は無限境界を明示したい場合 (テンプレートコード等) のために残されています.
注意 片側制約は必ず 式を左辺に置いて 書きます.
n >= exprはコンパイルエラー (= deleteで明示的に禁止,エラーメッセージは "use of deleted function"). 逆向きのn <= exprは単独ではエラーにできません (チェインn <= expr <= mの 開始部と同じトークン列のため).ただしstd::pairを返すので+,*,sqr(),simplify_*()等を一切持たず,その後の操作で必ずコンパイルエラーになります.
配列 arr に対しても要素ごとに arr <= n / arr >= n が動作し,各要素の pos_sum() / neg_sum() を自動的に他側境界とします (補助変数のサイズが要素ごとに最小化されます).
二乗関数: sqr()
qbpp::Exprオブジェクトfに対して:
qbpp::sqr(f)(グローバル関数): 式f * fを返します. 引数fは非整数のExprTypeオブジェクトでもかまいません.f.sqr()(メンバ関数):fをその場でf * fに置き換え,更新された式を返します.
最大公約数関数gcd()
グローバル関数**gcd()**はqbpp::Exprオブジェクトを引数として取り,すべての整数係数と整数定数項の最大公約数(GCD)を返します.
与えられたqbpp::Exprオブジェクトは結果のGCDで割り切れるため,すべての整数係数と整数定数項をGCDで除算しても,式の構造や最適解は変わりません.
簡約化関数: simplify(), simplify_as_binary(), simplify_as_spin()
qbpp::Exprオブジェクトfに対して,メンバ関数**f.simplify()**は以下の操作をその場で実行します:
各項内の変数を一意な変数IDに従ってソート
重複する項をマージ
項を以下の規則でソート:
低次の項が先に現れる
同じ次数の項は辞書順に並べる
グローバル関数**qbpp::simplify(f)**はfを変更せずに同じ操作を実行します.
バイナリとスピンの簡約化
簡約化関数の2つの特殊なバリアントが提供されています:
simplify_as_binary(): すべての変数がバイナリ値\(\lbrace 0,1\rbrace\)を取ることを仮定して簡約化が実行されます. 恒等式\(x^2=x\)がすべての変数\(x\)に適用されます.simplify_as_spin()すべての変数がスピン値\(\lbrace -1,+1\rbrace\)を取ることを仮定して簡約化が実行されます. 恒等式\(x^2=1\)がすべての変数\(x\)に適用されます.
両方のバリアントはメンバ関数とグローバル関数として利用できます:
メンバ関数: その場で簡約化を実行し,
fを更新します.f.simplify_as_binary()f.simplify_as_spin()
グローバル関数: fを変更せずに簡約化された式を返します.
qbpp::simplify_as_binary(f)qbpp::simplify_as_spin(f)
評価関数
**qbpp::MapList**オブジェクトは,qbpp::Varオブジェクトと整数のペアのリストを格納します.
各ペアは変数から整数値へのマッピングを定義します.
qbpp::Exprオブジェクトfとqbpp::MapListオブジェクトmlに対して,評価関数f(ml)はmlで指定された変数割り当ての下でfの値を評価し,結果の整数値を返します.
fに現れるすべての変数は,mlに対応するマッピングが定義されていなければなりません.
置換関数: replace()
**qbpp::MapList**オブジェクトには,qbpp::VarオブジェクトとExprTypeオブジェクトのペアも含めることができます.
このようなペアは変数から式へのマッピングを定義します.
qbpp::Exprオブジェクトfとqbpp::MapListオブジェクトmlに対して:
qbpp::replace(f, ml):fを変更せずに,mlのマッピングに従ってf内の変数を置換した新しいqbpp::Exprオブジェクトを返します.f.replace(ml):mlのマッピングに従ってf内の変数をその場で置換し,結果のqbpp::Exprオブジェクトを返します.
バイナリ/スピン変換関数: spin_to_binary(), binary_to_spin()
xをバイナリ変数,sをスピン変数とします.
x = 1であるとき,かつそのときに限りs = 1であると仮定します.
この仮定の下で,以下の関係が成り立ちます:
\(f(s)\)をスピン変数\(s\)の関数とします. このとき,関数\(g(x)=f(2x-1)\)は上記の関係の下で同じ値を与えるバイナリ変数\(x\)の関数です.
**spin_to_binary()**関数はこの関係を使用して,スピン変数の関数を表すqbpp::Exprオブジェクトをバイナリ変数の関数を表す等価なqbpp::Exprオブジェクトに変換します.
具体的には,f内のすべてのスピン変数sを2 * s - 1に置換します.
qbpp::spin_to_binary(f):f内のすべてのスピン変数sを2 * s - 1に置換した新しいqbpp::Exprオブジェクトを生成して返します.f.spin_to_binary():qbpp::spin_to_binary(f)を使用してfをその場で更新し,更新された式を返します.
同様に,**binary_to_spin()**関数はf内のすべてのバイナリ変数xを(x + 1) / 2に置換します.
結果の式には非整数の係数が含まれる場合があります.
そのため,すべての係数が整数になるように,式全体が\(2^d\)(\(d\)はすべての項の最大次数)で乗算されます.
spin_to_binary()と同様に,binary_to_spin()にもグローバル関数とメンバ関数の両方のバリアントが提供されています.
非線形関数: abs(), relu(), max(), min()
式の中で絶対値・ReLU・最大値・最小値を直接使えます.これらを含む式は, Hi-QUBO に同梱されているソルバーが関数値を直接扱って探索します (補助変数やペナルティ多項式を自分で設計する必要はありません). 詳しくは非線形関数を参照してください.
qbpp::abs(f)/qbpp::abs(f, k): \(|f|\) / \(|f|^k\) を表すqbpp::Exprを返します(kは 1 か 2).qbpp::relu(f)/qbpp::relu(f, k): \(\max(0, f)\) / \(\max(0, f)^k\) を表すqbpp::Exprを返します(kは 1 か 2).qbpp::max(f, g)/qbpp::min(f, g): 2 つの式の最大値・最小値を表すqbpp::Exprを返します.
これらの関数を含む式には次の制限があります:
係数は任意の定数で加減・スケールできます(負の係数は関数値を最大化する 向きに働きます).ただし MIP ソルバーでは正の係数のみ使えます.
expand_cons()・reduce()・binarize()には対応していません.同梱ソルバーはすべての関数に対応します.MIP ソルバーでは, GurobiSolver と ScipSolver(Quadratic 方式)が
abs・relu(k= 1, 2)に,線形化方式の MIP ソルバーがabs・relu(k= 1)に 対応します.max・minは凸方向(w*maxの最小化・min の最大化)なら MIP ソルバーでも解けます.
HUBO→QUBO 変換関数: reduce()
reduce()関数は,HUBO 式(3 次以上の項を含みうる多項式)を等価な QUBO 式(次数 2 以下)に変換します.次数が 3 以上のすべての項は,新しい補助二値変数を導入して二次式に書き換えられます.
この変換は最適値を保存します.元の変数の任意の割当に対して,変換後の式を補助変数について最小化した値が,元の HUBO の値に一致します.したがって変換後 QUBO の最小解を元の変数に射影したものは,元の HUBO の最小解になります.否定リテラルを含む HUBO 式も自動的に処理され,変換後の QUBO は正リテラルのみになります.
qbpp::reduce(f):fと等価な,次数 2 以下の新しいqbpp::Exprオブジェクトを生成して返します.f.reduce():qbpp::reduce(f)を使用してfをその場で更新し,更新された式を返します.
#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");
qbpp::Expr f = a * b * c * d - 2 * a * b + 3; // 4 次の項を含む HUBO
std::cout << "f = " << f << std::endl;
qbpp::Expr g = qbpp::reduce(f); // 等価な QUBO
std::cout << "g = " << g << std::endl;
std::cout << "max degree = " << g.max_degree() << std::endl;
}
二次のモデルしか受け付けないバックエンド(例: 一部の物理アニーラ)でモデルを解く場合に有用です.
タプルインデックス a(...)
Array の operator() で Python タプルインデックス風のサブ配列取得ができます.各引数は軸ごとに以下のいずれか:
引数 |
意味 |
次元変化 |
|---|---|---|
整数 |
その軸を |
軸が削除される |
|
全範囲 |
軸を保持 |
|
範囲 |
軸を保持 |
|
軸サイズから計算される位置 |
固定または範囲端 |
指定しなかった末尾の軸は自動的に qbpp::all とみなされます.
出力は内部で 1 パスで構築するため,結果サイズに比例した O(output_size) のコピーコストになります.
使用例
// 2D 配列
auto x = qbpp::var("x", 3, 4);
auto row0 = x(0); // axis 0 を 0 に固定 → 1D (4,)
auto col2 = x(qbpp::all, 2); // axis 1 を 2 に固定 → 1D (3,)
auto sub = x(qbpp::slice(0, 2), qbpp::slice(1, 3)); // 2D (2, 2)
// 3D 配列
auto y = qbpp::var("y", 2, 3, 4);
auto s = y(1, qbpp::all, 3); // axis 0,2 を固定 → 1D (3,)
auto v = y(1, 2, 3); // 全軸固定 → Var
// end キーワード(MATLAB 風)
auto last5 = x(qbpp::slice(qbpp::end - 5, qbpp::end)); // 末尾 5 要素
auto mid = x(qbpp::all, qbpp::slice(1, qbpp::end - 1)); // 内側のみ
詳細と例については スライス関数と連結関数 を参照してください.
連結関数: concat()
連結関数は配列の結合やスカラーの追加・先頭追加を行います.
配列 + 配列
qbpp::concat(a, b): 同じ型の2つの配列を最外次元に沿って連結します.
スカラー + 配列 / 配列 + スカラー
qbpp::concat(scalar, v): 配列の先頭にスカラーを追加します.qbpp::concat(v, scalar): 配列の末尾にスカラーを追加します.
スカラーはqbpp::Exprに暗黙的に変換されます.
2次元の次元指定付き連結
qbpp::concat(a, b, dim): 2つの2次元配列を指定した次元に沿って連結します.dim=0: 行方向の連結(行を追加)dim=1: 列方向の連結(列を追加.両方の行数が同じである必要があります)
例: 境界差分
auto x = qbpp::var("x", 4);
auto diff = qbpp::concat(1, x) - qbpp::concat(x, 0);
// diff = {1-x[0], x[0]-x[1], x[1]-x[2], x[2]-x[3], x[3]-0}
Term メンバ関数
以下の qbpp::Term のメンバ関数は,項の内部構造への読み取り専用アクセスを提供します.
式 |
戻り値の型 |
説明 |
|---|---|---|
|
|
係数を返す |
|
|
次数(変数の数)を返す |
|
|
|
|
|
|
例
auto x = qbpp::var("x");
auto y = qbpp::var("y");
auto z = qbpp::var("z");
qbpp::Term t = 3 * x * y;
t.coeff(); // 3
t.degree(); // 2
t.var(0); // x
t.var(1); // y
t.has(x); // true
t.has(z); // false
Expr メンバ関数
以下の qbpp::Expr のメンバ関数は,式の内部構造への読み取り専用アクセスを提供します.
式 |
戻り値の型 |
説明 |
|---|---|---|
|
|
定数項を返す |
|
|
項の数を返す(定数項を除く) |
|
|
次数 |
|
|
|
|
|
すべての項の最大次数を返す |
|
|
|
|
|
整数変数 |
例
auto x = qbpp::var("x");
auto y = qbpp::var("y");
qbpp::Expr f = qbpp::simplify(3 * x + 2 * x * y + 5);
// f = 5 + 3*x + 2*x*y
f.constant(); // 5
f.term_count(); // 2
f.term(0); // 3*x
f.term(1); // 2*x*y
f.term(1).coeff(); // 2
f.term(1).var(0); // x
f.term(1).var(1); // y
f.max_degree(); // 2
f.has(x); // true
f.has(y); // true
以下の表は,式(pyqbpp.Expr)を対象とする演算子と関数をまとめたものです.
ここで「式」は VAREXPR で説明した3つの概念(整数・変数・式)で構成される多項式を指します.
引数の型の 式 列は,整数・変数・式のいずれでも受け付けられることを意味します.
グローバル関数と In-place メソッドの規則
式に対する処理は,原則として次の2形式を対で提供します:
グローバル関数
qbpp.func(f, ...)— 非破壊.fを変更せず,新しい結果オブジェクトを返します.In-place メンバ
f.func(...)—fを処理結果で上書きし,selfを返します(メソッドチェーンできます).
「f をそのまま使い続けたい」ときはグローバル,「f を処理後の値に置き換えたい」ときはメンバを使います.
戻り値の型がグローバル側と異なる関数でも,メンバ版は「結果を self に書き戻す」設計に統一されています.
例: qbpp.gcd(f) は整数を返しますが,f.gcd() は f 自身を「その gcd の値を持つ定数式」に上書きします.
演算子・関数一覧
演算子/関数 |
構文 |
種別 |
戻り値の型 |
引数の型 |
|---|---|---|---|---|
コピー |
|
Global |
式 |
整数・変数・式 |
二項演算子 |
|
Global |
式 |
式 ⊕ 式 |
複合代入 |
|
In-place |
式 |
式 |
除算 |
|
Global |
式 |
式, 整数 |
複合除算 |
|
In-place |
式 |
整数 |
単項演算子 |
|
Global |
式 |
式 |
等価制約 |
|
Global |
制約式 |
式, 整数 |
範囲制約 |
|
Global |
制約式 |
式, 整数, 整数 |
上限制約 |
|
Global |
制約式 |
式, 整数 |
下限制約 |
|
Global |
制約式 |
式, 整数 |
二乗 |
|
Global |
式 |
式 |
二乗 |
|
In-place |
式 |
— |
最大公約数 |
|
Global |
整数 |
式 |
最大公約数 |
|
In-place |
式(定数で上書き) |
— |
簡約化 |
|
Global |
式 |
式 |
簡約化 |
|
In-place |
式 |
— |
バイナリ簡約化 |
|
Global |
式 |
式 |
バイナリ簡約化 |
|
In-place |
式 |
— |
スピン簡約化 |
|
Global |
式 |
式 |
スピン簡約化 |
|
In-place |
式 |
— |
評価 |
|
Global |
整数 |
式, dict |
置換 |
|
Global |
式 |
式, dict |
置換 |
|
In-place |
式 |
dict |
スピン → バイナリ変換 |
|
Global |
式 |
式 |
スピン → バイナリ変換 |
|
In-place |
式 |
— |
バイナリ → スピン変換 |
|
Global |
式 |
式 |
バイナリ → スピン変換 |
|
In-place |
式 |
— |
HUBO→QUBO 変換 |
|
Global |
式 |
式 |
HUBO→QUBO 変換 |
|
In-place |
式 |
— |
スライス |
|
Global |
array |
array |
連結 |
|
Global |
array |
array/スカラーのリスト |
代入: g = f と g = qbpp.copy(f)
Python の = は名前の束縛であって値のコピーではありません.
つまり g = f は「新しい名前 g を f が指すのと同じオブジェクトに結びつける」だけで,
f と g は同一の式オブジェクトを共有します.
一方,g = qbpp.copy(f) は f と独立した新しい式を作ります.
この違いは,その後で in-place な操作(複合代入や in-place メンバ) を行ったときに顕在化します.
import pyqbpp as qbpp
x = qbpp.var("x")
y = qbpp.var("y")
# --- g = f: 参照共有 ---
f = 2 * x + 3 * y
g = f # g と f は同じオブジェクト (g is f → True)
g += 100
print(f) # 100 +2*x +3*y ← f も変わってしまう
print(g) # 100 +2*x +3*y
# --- g = qbpp.copy(f): 独立コピー ---
f = 2 * x + 3 * y
g = qbpp.copy(f) # g は f の内容を持つ別オブジェクト (g is f → False)
g += 100
print(f) # 2*x +3*y ← f は不変
print(g) # 100 +2*x +3*y
注釈 右辺が
f + 1,2 * f,-fなどの二項・単項演算の場合は, 演算子がそのたびに新しい式を返すので,結果は自動的にfと独立したオブジェクトになります. 注意が必要なのは「g = fの後にgを in-place で書き換える」パターンだけです. 独立したコピーが欲しいときはqbpp.copy(f)を使ってください.
注釈 右辺が in-place メンバ呼び出しだけ の場合にも同じ罠があります. In-place メンバは「
f自身を処理結果で上書きしてselfを返す」ので,g = f.sqr()は「新しい式をgに代入」ではなく「fを2乗して書き換え,そのfをgにも別名として付ける」結果になります. 典型的には次の3通りで結果が異なります:
書き方
効果
g = f.sqr()
fがその場で2乗され,gはfと同じオブジェクトを指す(g is f→ True)
g = qbpp.sqr(f)グローバル形(非破壊).
fは不変,gは独立した新しい式
g = qbpp.copy(f.sqr())
fは2乗された後,その独立コピーがgに入る(f側の書き換えは残る)「
fを保ったまま2乗結果を得たい」ときは グローバル形qbpp.sqr(f)を使うのが最も自然です. 他の in-place メンバ(simplify_as_binary,replace,spin_to_binaryなど)にも同じルールが当てはまります.
注釈
qbpp.copy()は整数・変数に対しても安全に呼び出せます. これらはイミュータブルなので共有されても書き換えで影響を受けず,copy()は元のオブジェクトをそのまま返します. したがって型を気にせずcopy()を使えます.
二項演算子: +, -, *
+, -, * は整数・変数・式を自由に組み合わせて受け付け,結果の式を返します.
オペランドの種類を意識せずに 2 * x * y - x + 1 のように自然に書けます.
複合代入演算子: +=, -=, *=
左辺は式でなければなりません.右辺には整数・変数・式のいずれも渡せ, 指定された演算が適用されて左辺の式がその場で更新されます.
除算 / と複合除算 /=
除算演算子 / は 被除数 として式を,除数 として整数を取り,商 を新しい式として返します.
被除数の式は除数で割り切れなければなりません.すなわち, 式の整数定数項とすべての整数係数が除数で割り切れる必要があります.
複合除算演算子 /= は式をその場で除算します.
例
import pyqbpp as qbpp
x = qbpp.var("x")
y = qbpp.var("y")
f = 6 * x + 4 * y + 2
g = f / 2 # g = 3*x + 2*y + 1 (新しい Expr)
f /= 2 # f = 3*x + 2*y + 1 (in-place)
等価・範囲制約: constrain()
constrain() 関数は,式 f に対する制約をペナルティ式として表現します.
等価制約と範囲制約の両方を統一的に記述できます.
g = qbpp.constrain(f, equal=n) # f == n のペナルティ式
g = qbpp.constrain(f, between=(l, u)) # l <= f <= u のペナルティ式
g = qbpp.constrain(f, between=(l, None)) # l <= f のペナルティ式(上限なし)
g = qbpp.constrain(f, between=(None, u)) # f <= u のペナルティ式(下限なし)
ここで f は式,n / l / u は整数です.
いずれの形式も,制約が満たされたときに最小値 0 となる式を返します.
equal=n:sqr(f - n)を返します.between=(l, u): 範囲[l, u-1]の値を取る単位間隔の補助整数変数aが暗黙的に導入され,関数は(f - a) * (f - (a + 1))を返します.between=(l, None)/between=(None, u):l/uの一方のみを制約する半開区間です.
制約式
constrain() が返すオブジェクト g は,penalty + body のメタデータを持つ pyqbpp.Expr です.
gはペナルティ式そのものを表し,通常の式として評価・簡約化・ソルバー入力に使えます.g.bodyは制約を作る前の元の式fを返します.
演算子による省略記法: ==, <=, >=, qbpp.same
上記の4種類の制約は Python の比較演算子を用いてより簡潔に書けます.
省略記法と明示的な constrain() 呼び出しは同じペナルティ式を構築するため,どちらを使っても自由に混在させられます.
省略記法 |
同等の |
意味 |
|---|---|---|
|
|
\(f = n\) |
|
|
\(l \le f \le u\) |
|
|
\(f \le u\) |
|
|
\(l \le f\) |
f は式,n / l / u は整数です.いずれも対応する constrain() 呼び出しと同じ制約式(.body も同じ)を返します.
両側制約では qbpp.same をプレースホルダとして用い,「& の反対側と同じ式」を意味します.2 * x + 3 * y のような長い式を (0 <= 2 * x + 3 * y) & (2 * x + 3 * y <= 12) のように二度書く手間を省け,左右で異なる式を書いてしまうミスも防げます.(なお (l <= f) & (f <= u) のように body を二度書く形式は TypeError として明示的に拒否されます.たとえ両辺が見た目には同じでも,別々にインラインで書かれた式は別オブジェクトになるため,body の比較は意図的にオブジェクト同一性で行っています.)
省略記法は 配列に対しても要素ごとに動作します.arr == 1 や (0 <= arr) & (qbpp.same <= 1) は,qbpp.constrain(arr, ...) と同じく制約式の配列を返します.
注釈 Python では
&の優先順位が<=/>=より高いため,両側制約は必ず各比較を括弧で囲んでください:(l <= f) & (qbpp.same <= u)であってl <= f & qbpp.same <= uではありません. 省略記法を+,*などと組み合わせる際も同様で,例えば100 * ((0 <= f) & (qbpp.same <= u))のように括弧が必要です(100 * (0 <= f) & (qbpp.same <= u)は誤り).
例
import pyqbpp as qbpp
x = qbpp.var("x", between=(0, 10))
y = qbpp.var("y", between=(0, 10))
eq = (x + y == 5) # x + y = 5
range_= (0 <= 2 * x + 3 * y) & (qbpp.same <= 12) # 0 <= 2x + 3y <= 12
upper = (x + y <= 8) # x + y <= 8
lower = (x + y >= 2) # x + y >= 2
f = -x - y + 100 * (eq + range_ + upper + lower)
f.simplify_as_binary()
二乗関数: sqr()
式 f に対して:
pyqbpp.sqr(f)(グローバル関数):f * fを計算して返します. 引数fは整数・変数・式のいずれでも構いません.
配列 v に対して:
pyqbpp.sqr(v): 各要素を二乗した新しい配列を返します.
例
import pyqbpp as qbpp
x = qbpp.var("x")
f = qbpp.sqr(x) # x * x
最大公約数関数: gcd()
式 f のすべての整数係数と整数定数項の最大公約数(GCD)を計算します.
グローバル形と in-place 形の2種類があります.
qbpp.gcd(f)(グローバル,非破壊):fを変更せず,GCD を整数値として返します.f.gcd()(メンバ,in-place):f自身を「その GCD の値を持つ定数式」に上書きします(返り値はself).
式を GCD で約分したい場合は,グローバル形と複合除算を組み合わせて f /= qbpp.gcd(f) と書きます.
例
import pyqbpp as qbpp
x = qbpp.var("x")
y = qbpp.var("y")
f = 6 * x + 4 * y + 2
# グローバル: 値だけを取得、f は変更されない
print(qbpp.gcd(f)) # 2
print(f) # 2 +6*x +4*y
# 約分: f を更新したい場合は /= と組み合わせる
g = qbpp.copy(f)
g /= qbpp.gcd(g) # g = 1 +3*x +2*y
print(g)
# In-place: f を GCD の定数式で上書き
h = qbpp.copy(f)
h.gcd() # h = 2 (定数式)
print(h)
簡約化関数: simplify(), simplify_as_binary(), simplify_as_spin()
式 f に対して,メンバ関数 f.simplify() は以下の操作をその場で行います.
各項内の変数を一意な変数IDに従ってソート
重複する項をマージ
項を以下のようにソート:
低次の項が先に配置される
同次の項は辞書順で並べられる
グローバル関数 pyqbpp.simplify(f) は f を変更せずに同じ操作を行います.
バイナリとスピンの簡約化
簡約化関数の2つの特殊なバリアントが提供されています.
simplify_as_binary(): すべての変数がバイナリ値 \(\lbrace 0,1\rbrace\) を取ることを前提として簡約化を行います. すべての変数 \(x\) に対して恒等式 \(x^2=x\) が適用されます.simplify_as_spin(): すべての変数がスピン値 \(\lbrace -1,+1\rbrace\) を取ることを前提として簡約化を行います. すべての変数 \(x\) に対して恒等式 \(x^2=1\) が適用されます.
両方のバリアントはメンバー関数とグローバル関数として利用可能です.
メンバー関数(その場で更新):
f.simplify_as_binary(),f.simplify_as_spin()グローバル関数(非破壊的):
qbpp.simplify_as_binary(f),qbpp.simplify_as_spin(f)
例
import pyqbpp as qbpp
x = qbpp.var("x")
f = x * x + x
f.simplify_as_binary() # 2*x (since x^2 = x)
g = x * x + x
g.simplify_as_spin() # 1 + x (since x^2 = 1)
評価関数: f(ml)
評価関数は {変数: 値} の辞書を受け取ります.各エントリは変数から整数値へのマッピングを定義します.
式 f と辞書 ml に対して,評価関数 f(ml) は ml で指定された変数の割り当ての下で f の値を評価し,結果の整数値を返します.
f に出現するすべての変数は,ml に対応するマッピングが定義されていなければなりません.
例
import pyqbpp as qbpp
x = qbpp.var("x")
y = qbpp.var("y")
f = 3 * x + 2 * y + 1
print(f({x: 1, y: 0})) # 4 (= 3*1 + 2*0 + 1)
置換関数: replace()
replace() 関数は {変数: 式} の辞書を受け取ります.値側には整数も指定できます.
式 f と辞書 ml に対して:
pyqbpp.replace(f, ml)(グローバル関数):fを変更せずに,mlのマッピングに従ってfの変数を置換した新しい式を返します.f.replace(ml)(メンバ関数):mlのマッピングに従ってfの変数をその場で置換し,結果の式を返します.
辞書の作成
import pyqbpp as qbpp
ml = {x: 0, y: 1} # {変数: 式} の辞書
ml = {x: 0, y: z} # 値には変数も指定可能
例
import pyqbpp as qbpp
x = qbpp.var("x")
y = qbpp.var("y")
f = 2 * x + 3 * y + 1
ml = {x: 1, y: 0}
g = qbpp.replace(f, ml) # g = 2*1 + 3*0 + 1 = 3 (new Expr)
f.replace(ml) # f is modified in place
バイナリ/スピン変換関数: spin_to_binary(), binary_to_spin()
x をバイナリ変数,s をスピン変数とします.
x = 1 と s = 1 が同値であると仮定します.
この仮定の下で,以下の関係が成り立ちます.
spin_to_binary() 関数は,すべてのスピン変数 s を 2 * s - 1 で置換することにより,スピン変数の式をバイナリ変数の式に変換します.
binary_to_spin() 関数は,すべてのバイナリ変数 x を (x + 1) / 2 で置換することにより,バイナリ変数の式をスピン変数の式に変換します.
すべての係数が整数のままになるように,結果の式は \(2^d\)(\(d\) は最大次数)で乗算されます.
両方の関数はメンバー関数(その場で更新)とグローバル関数(非破壊的)として利用可能です.
例
import pyqbpp as qbpp
s = qbpp.var("s")
f = 3 * s + 1
g = qbpp.spin_to_binary(f) # -2 + 6*s (replaced s with 2*s-1)
b = qbpp.var("b")
h = 2 * b + 1
k = qbpp.binary_to_spin(h) # 4 + 2*b (replaced b with (b+1)/2, multiplied by 2)
非線形関数: abs(), relu(), max(), min()
式の中で絶対値・ReLU・最大値・最小値を直接使えます.これらを含む式は, Hi-QUBO に同梱されているソルバーが関数値を直接扱って探索します (補助変数やペナルティ多項式を自分で設計する必要はありません). 詳しくは非線形関数を参照してください.
qbpp.abs(f)/qbpp.abs(f, k): \(|f|\) / \(|f|^k\) を表すExprを返します(kは 1 か 2).qbpp.relu(f)/qbpp.relu(f, k): \(\max(0, f)\) / \(\max(0, f)^k\) を表すExprを返します(kは 1 か 2).qbpp.max(f, g)/qbpp.min(f, g): 2 つの式の最大値・最小値を表すExprを返します.
これらの関数を含む式には次の制限があります:
係数は任意の定数で加減・スケールできます(負の係数は関数値を最大化する 向きに働きます).
expand_cons()・reduce()・binarize()には対応していません.同梱ソルバーはすべての関数に対応します.MIP ソルバーでは GurobiSolver が
abs・relu(k= 1, 2)とmax・minの 凸方向(w*maxの最小化・min の最大化)に対応します(係数は正のみ). 他の MIP ラッパーは非線形関数に対応していません.
注:
qbpp.abs/qbpp.max/qbpp.minは Python 組み込みのabs/max/minとは別の関数です.from pyqbpp import *を 使う場合は名前の衝突に注意してください.
HUBO→QUBO 変換関数: reduce()
reduce() 関数は,HUBO 式(3 次以上の項を含みうる多項式)を等価な QUBO 式(次数 2 以下)に変換します.次数が 3 以上のすべての項について,新しい補助二値変数を導入して二次式に書き換えます.
この変換は最適値を保存します.元の変数の任意の割当に対して,変換後の式を補助変数について最小化した値が,元の HUBO の値に一致します.したがって変換後 QUBO の最小解を元の変数に射影したものは,元の HUBO の最小解になります.否定リテラルを含む式も自動的に処理され,変換後の QUBO は正リテラルのみになります.
reduce() はグローバル関数(非破壊)とメンバ関数(in-place)の両方が提供されます.
例
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 - 2 * a * b + 3 # 4 次の項を含む HUBO
g = qbpp.reduce(f) # 等価な QUBO
print("max degree =", g.max_degree)
二次のモデルしか受け付けないバックエンド(例: 一部の物理アニーラ)でモデルを解く場合に有用です.
スライス関数: v[from:to]
Pythonのスライス記法で array から部分範囲を抽出します.スライスは新しい array を返します.
v[from:to]: 最外次元の[from, to)の要素.v[:n]: 先頭n個.v[-n:]: 末尾n個.
多次元配列にはタプルインデックスで内側の次元をスライス:
v[:, from:to]: 各行をスライス(dim=1).v[:, :, from:to]: dim=2 でスライス.任意の深さで動作.
例
import pyqbpp as qbpp
x = qbpp.var("x", shape=(3, 5))
print(x[:, :3]) # 各行の先頭3列
print(x[1:3, 2:4]) # 1-2行, 2-3列
連結関数: concat()
concat() 関数は配列とスカラーのリストを指定した軸で連結します.
qbpp.concat([a, b, c, ...], axis=0): 軸axisに沿ってリスト内の各要素を連結.リスト要素は array とスカラー(整数・変数・式)の混在が可能.スカラーは他の配列の形状に合わせて軸方向にブロードキャストされます.
要素型の異なる array 同士を連結する場合は自動的に式の array に昇格されます.
例
import pyqbpp as qbpp
x = qbpp.var("x", shape=4)
y = qbpp.concat([1, x, 0])
# y = [1, x[0], x[1], x[2], x[3], 0]
z = qbpp.var("z", shape=(3, 4))
zg = qbpp.concat([1, z, 0], axis=1)
# 各行: [1, z[i][0], ..., z[i][3], 0]
式のメンバ
以下のメンバは式 f の内部構造に読み取り専用でアクセスします.
式 |
戻り値の型 |
説明 |
|---|---|---|
|
整数 |
定数項を返す(プロパティ) |
|
整数 |
すべての項の最大次数を返す(プロパティ) |
|
整数 |
項の数を返す(定数項を除く) |
|
整数 |
次数 |
|
単項の式 |
|
|
|
変数 |
f.term(i) が返す「単項の式」 t には,さらに以下のアクセッサがあります:
式 |
戻り値の型 |
説明 |
|---|---|---|
|
整数 |
係数を返す(プロパティ) |
|
整数 |
その項の次数(変数の数)を返す(プロパティ) |
|
|
その項の |
|
|
変数 |
例
import pyqbpp as qbpp
x = qbpp.var("x")
y = qbpp.var("y")
f = qbpp.simplify(3 * x + 2 * x * y + 5)
# f = 5 + 3*x + 2*x*y
f.constant # 5
f.term_count() # 2
f.max_degree # 2
f.has(x) # True
f.has(y) # True
t = f.term(1) # 2*x*y (単項の式)
t.coeff # 2
t.degree # 2
t.var(0) # x
t.var(1) # y
t.has(x) # True