多次元の変数と式

多次元変数の定義

Hi-QUBO は、多次元の変数(qbpp::Var オブジェクト)および多次元の整数変数(qbpp::VarInt オブジェクト)を、任意の次元数で定義できます。それぞれ qbpp::var() および qbpp::var_int() を使用します。

基本的な使用方法は次のとおりです。

  • qbpp::var("name", s1, s2, ..., sd)
    指定した名前nameと形状 \(s1 \times s2 \times \cdots \times sd\) を持つ qbpp::Var オブジェクトの多次元配列を作成します。

  • l <= qbpp::var_int("name", s1, s2, ..., sd) <= u
    指定した範囲 [l, u] と形状 \(s1 \times s2 \times \cdots \times sd\) を持つ qbpp::VarInt オブジェクトの多次元配列を作成します。

次の Hi-QUBO プログラムは、次元 \(2 \times 3 \times 4\) の2値変数と整数変数をそれぞれ作成します。

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

int main() {
  auto x = qbpp::var("x", 2, 3, 4);
  auto y = 1 <= qbpp::var_int("y", 2, 3, 4) <= 8;

  std::cout << "x : " << x << std::endl;
  std::cout << "y : " << y << std::endl;
}

このプログラムは次の出力を生成します。

x : {{{x[0][0][0],x[0][0][1],x[0][0][2],x[0][0][3]},{x[0][1][0],x[0][1][1],x[0][1][2],x[0][1][3]},{x[0][2][0],x[0][2][1],x[0][2][2],x[0][2][3]}},{{x[1][0][0],x[1][0][1],x[1][0][2],x[1][0][3]},{x[1][1][0],x[1][1][1],x[1][1][2],x[1][1][3]},{x[1][2][0],x[1][2][1],x[1][2][2],x[1][2][3]}}}
y : {{{1 +y[0][0][0][0] +2*y[0][0][0][1] +4*y[0][0][0][2], ... }}}

x の各 qbpp::Var オブジェクトは x[i][j][k] のようにアクセスできます。

y の各 qbpp::VarInt オブジェクトも y[i][j][k] のようにアクセスできますが、内部的には次の3つの2値変数で表現されています。

y[i][j][k][0]
y[i][j][k][1]
y[i][j][k][2]

これらは、指定された整数範囲を2進数でエンコードしたものに対応します。

配列ファクトリ (qbpp::array<T>)

qbpp::array<T> は配列を作成する汎用ファクトリ関数です。要素型 T には qbpp::coeff_t(整数定数)、qbpp::Varqbpp::Termqbpp::Expr を指定できます。 初期化子から T を推論できる場合はテンプレート引数を省略できます.

呼び出し形式

戻り値

説明

qbpp::array<coeff_t>(s1, s2, ..., sd)

qbpp::Array<d, qbpp::coeff_t>

形状指定でゼロ初期化された整数配列

qbpp::array<qbpp::Expr>(s1, s2, ..., sd)

qbpp::Array<d, qbpp::Expr>

形状指定でゼロ初期化された式配列(qbpp::expr(s1, s2, ..., sd) と等価)

qbpp::array({v1, v2, ...})

qbpp::Array<1, qbpp::coeff_t>

1次元整数定数配列(T 推論)

qbpp::array({{a,b},{c,d}})

qbpp::Array<2, qbpp::coeff_t>

2次元整数定数配列(T 推論)

qbpp::array<qbpp::Expr>({v1, v2, ...})

qbpp::Array<1, qbpp::Expr>

int 値を Expr に昇格

qbpp::array<qbpp::Expr>({{a,b},{c,d}})

qbpp::Array<2, qbpp::Expr>

2次元 int 値を Expr に昇格

形状のみを指定する形(qbpp::array<T>(s1, s2, ...))では、次元数からは T を 推論できないためテンプレート引数の明示が必要です。

整数定数配列は変数配列との要素ごとの演算に使用できます。以下のプログラムは、\(2 \times 2\) の整数定数行列 c とバイナリ変数行列 x の要素ごとの積を合計します:

multi-dimensional-variables-and-expressions-program6.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto c = qbpp::array({{1, 2}, {3, 4}});
  auto x = qbpp::var("x", 2, 2);
  auto f = qbpp::sum(c * x);
  std::cout << "f = " << f << std::endl;
}

ここで c の型は qbpp::Array<2, qbpp::coeff_t>(2次元の整数定数配列)、x の型は qbpp::Array<2, qbpp::Var> です。 c * x は要素ごとの積を2次元の項の配列(qbpp::Array<2, qbpp::Term>)として返し、qbpp::sum がその全要素を合計して単一の Expr を返します。このプログラムの出力は以下の通りです:

f = x[0][0] +2*x[0][1] +3*x[1][0] +4*x[1][1]

個別の範囲を持つ整数変数配列の作成

多次元整数変数配列を定義する場合、l <= qbpp::var_int("name", s1, s2, ...) <= u で作成された全要素は同じ範囲 \([l, u]\) を共有します。 しかし実際の問題では、各要素に異なる範囲が必要な場合が多くあります。

このような場合、まず qbpp::var_int("name", s1, s2, ...) == 0 でプレースホルダ配列を作成し、ループ内で各要素に個別の範囲を割り当てます:

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

int main() {
  auto max_vals = qbpp::array({3, 7, 15, 5});
  auto x = qbpp::var_int("x", max_vals.size()) == 0;
  for (size_t i = 0; i < max_vals.size(); ++i) {
    x[i] = 0 <= qbpp::var_int() <= max_vals[i];
  }
  for (size_t i = 0; i < max_vals.size(); ++i) {
    std::cout << "x[" << i << "] = " << x[i] << std::endl;
  }
}

このプログラムでは、qbpp::var_int("x", 4) == 0 がプレースホルダとして定数 0 の VarInt オブジェクト4個の配列を作成します。 各要素は 0 <= qbpp::var_int() <= max_vals[i] で個別の範囲に再代入されます。

このテクニックは、各変数の上限が異なる切り出し問題などでよく使われます。

NOTE == 0 の構文は min_val = max_val = 0(定数 0 )の VarInt を作成するものであり、等号制約を作成するものではありません。 任意の整数定数を使用でき、例えば qbpp::var_int("x", 4) == 5 は定数 5 のプレースホルダを作成します。

多次元式の定義

Hi-QUBO では、qbpp::expr() を使用して任意の深さの多次元式(qbpp::Expr オブジェクト)を定義できます。

  • qbpp::expr(s1, s2, ..., sd)
    形状 \(s1 \times s2 \times \cdots \times sd\) を持つ qbpp::Expr の多次元配列を作成します。

次のプログラムでは、形状 \(2 \times 3 \times 4\) の3次元変数配列 x と、サイズ \(2 \times 3\) の2次元式配列 f を定義しています。

三重ループを用いて、各 f[i][j]
x[i][j][0], x[i][j][1], x[i][j][2], x[i][j][3] の和を加算しています。

multi-dimensional-variables-and-expressions-program3.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x", 2, 3, 4);
  auto f = qbpp::expr(2, 3);
  for (size_t i = 0; i < 2; ++i) {
    for (size_t j = 0; j < 3; ++j) {
      for (size_t k = 0; k < 4; ++k) {
        f[i][j] += x[i][j][k];
      }
    }
  }
  f.simplify_as_binary();
  std::cout << "f = " << f << std::endl;
  for (size_t i = 0; i < 2; ++i) {
    for (size_t j = 0; j < 3; ++j) {
      std::cout << "f[" << i << "][" << j << "] = " << f[i][j] << std::endl;
    }
  }
}

simplify_as_binary() は、多次元の qbpp::Expr 配列にも適用できます。
この場合、各要素に対して 要素ごと(element-wise)simplify_as_binary() が適用されます。

このプログラムは次の出力を生成します。

f = {{x[0][0][0] +x[0][0][1] +x[0][0][2] +x[0][0][3],x[0][1][0] +x[0][1][1] +x[0][1][2] +x[0][1][3],x[0][2][0] +x[0][2][1] +x[0][2][2] +x[0][2][3]},{x[1][0][0] +x[1][0][1] +x[1][0][2] +x[1][0][3],x[1][1][0] +x[1][1][1] +x[1][1][2] +x[1][1][3],x[1][2][0] +x[1][2][1] +x[1][2][2] +x[1][2][3]}}
f[0][0] = x[0][0][0] +x[0][0][1] +x[0][0][2] +x[0][0][3]
f[0][1] = x[0][1][0] +x[0][1][1] +x[0][1][2] +x[0][1][3]
f[0][2] = x[0][2][0] +x[0][2][1] +x[0][2][2] +x[0][2][3]
f[1][0] = x[1][0][0] +x[1][0][1] +x[1][0][2] +x[1][0][3]
f[1][1] = x[1][1][0] +x[1][1][1] +x[1][1][2] +x[1][1][3]
f[1][2] = x[1][2][0] +x[1][2][1] +x[1][2][2] +x[1][2][3]

auto 型推論による式配列の作成

qbpp::Expr オブジェクトの配列は、明示的に qbpp::expr() を呼び出さなくても作成できます。
関数呼び出しや算術演算の結果が配列形状を持つ場合、auto 型推論を用いることで、同じ形状を持つ qbpp::Expr の配列を定義できます。

次の QUBO++ プログラムでは、qbpp::Var オブジェクトの配列 x と同じ次元を持つ qbpp::Expr 配列 f を作成しています。

multi-dimensional-variables-and-expressions-program4.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x", 2, 3);
  auto f = x + 1;
  f += x - 2;
  f.simplify_as_binary();
  for (size_t i = 0; i < 2; ++i) {
    for (size_t j = 0; j < 3; ++j) {
      std::cout << "f[" << i << "][" << j << "] = " << f[i][j] << std::endl;
    }
  }
}

このプログラムでは、x\(2 \times 3\)qbpp::Var 配列として定義されています。

  • x + 1\(2 \times 3\)qbpp::Expr 配列を生成します。

  • それが auto 型推論によって f に代入されます。

  • その後、x - 2 が要素ごとに f に加算されます。

  • simplify_as_binary() によって各要素が2値変数仮定のもとで簡約されます。

このプログラムの出力は次のとおりです。

f[0][0] = -1 +2*x[0][0]
f[0][1] = -1 +2*x[0][1]
f[0][2] = -1 +2*x[0][2]
f[1][0] = -1 +2*x[1][0]
f[1][1] = -1 +2*x[1][1]
f[1][2] = -1 +2*x[1][2]

ベクトルおよび配列の実装

Hi-QUBO は、次元数と要素型(qbpp::Var, qbpp::Expr, qbpp::Term, または整数係数型)をコンパイル時に決定する多次元配列を提供しています。 実際には配列の型を手書きする必要はほとんどありません。ファクトリ関数と auto による型推論が適切な具体化を選んでくれて、要素ごとの +, -, *, 代入などの演算が宣言された要素型と整合しているかをコンパイラが検査します。 配列は operator[] のチェーン(例: x[i][j][k])による多次元インデックスアクセスと、要素ごとの算術演算を提供します。

配列は以下のファクトリ関数で作成します(Dim = d は次元数):

  • qbpp::var("name", s1, s2, ..., sd)qbpp::Array<d, qbpp::Var>: 多次元のバイナリ変数の配列。

  • qbpp::expr(s1, s2, ..., sd)qbpp::Array<d, qbpp::Expr>: ゼロ初期化された多次元の式の配列(qbpp::array<qbpp::Expr>(s1, s2, ..., sd) の別名)。

  • qbpp::array({v1, v2, ...})qbpp::Array<1, qbpp::coeff_t>: 1次元の整数定数の配列(T 推論)。

  • qbpp::array<qbpp::coeff_t>(s1, s2, ..., sd)qbpp::Array<d, qbpp::coeff_t>: ゼロ初期化された多次元の整数配列。qbpp::array<qbpp::Expr>(...) で式配列も作成できます(qbpp::expr(...) と同じ)。

  • l <= qbpp::var_int("name", s1, s2, ..., sd) <= uqbpp::Array<d, qbpp::Expr>: 多次元の整数変数の配列。

注意 — 型を明示的に書く必要がある場合 ローカル変数であれば auto が適切な qbpp::Array<Dim, T> の具体化を推論するので、型を手書きする必要はほとんどありません。 例外は 非 static なクラスのメンバ変数で、C++ は非 static メンバに auto を許さないため、qbpp::Array<2, qbpp::Expr> のような完全な型をメンバ宣言で明示する必要があります。

qbpp::Vector<T> の主なメンバ関数

qbpp::Vector<T> は、std::vector<T> と互換性を持つ以下のメンバ関数を提供します。

  • size() : ベクトルの要素数を返します。

  • resize() : ベクトルの要素数を変更します。

  • reserve() : ベクトルのためのメモリ領域を確保します。

  • push_back() : 要素をベクトルの末尾に追加します。

  • emplace_back() : 要素を構築し、末尾に追加します。

  • empty() : ベクトルが空であれば true を返します。

  • operator[] : 指定したインデックスの要素を返します。

  • begin(), end() : 要素へアクセス・操作するためのイテレータを返します。

注意 — 要素型と演算結果 算術演算はスカラー式と同じ規則で要素型を昇格させます:変数の配列に 1 を足すと式の配列になり、変数の配列同士の要素ごとの積は項の配列になります。一方、+=, -=, *= のような複合代入は左辺の要素型を変えません。したがって変数の配列に +=1 を足するとコンパイルエラーになります — auto f = x + 1; のように新しい式の配列を作って受け取ってください。

要素ごとの演算子

std::vector<T> とは異なり、qbpp::Vector<T> は次の演算子を**要素ごと(element-wise)**にサポートしています。

  • + : ベクトル同士、またはベクトルとスカラーの要素ごとの加算

  • - : ベクトル同士、またはベクトルとスカラーの要素ごとの減算

  • * : ベクトル同士、またはベクトルとスカラーの要素ごとの乗算

  • 単項 - : ベクトル内の全要素の符号を反転

さらに、多次元配列は qbpp::Vector<T> の入れ子インスタンスとして実装されています。例えば、以下のコードでの x のデータ型は qbpp::Vector<qbpp::Vector<qbpp::Var>> です:

auto x = qbpp::var("x", 2, 3);

上で説明した要素ごとの演算は、オペランドが同じ形状を持つ場合に限り、多次元配列でもサポートされます。

qbpp::Vector<T> はイテレータをサポートしているため、auto 型推論を用いた範囲ベースの for ループを以下のように使用できます:

multi-dimensional-variables-and-expressions-program5.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x", 2, 3);
  auto f = x + 1;
  f += x - 2;
  f.simplify_as_binary();
  std::cout << "total = " << f.total() << std::endl;
  std::cout << "ndim = " << f.ndim() << std::endl;
  std::cout << "shape = (" << f.shape(0) << ", " << f.shape(1) << ")" << std::endl;
  for (size_t i = 0; i < f.size(); ++i) {
    for (size_t j = 0; j < f[i].size(); ++j) {
      std::cout << "(" << f[i][j] << ")";
    }
    std::cout << std::endl;
  }
}

外側の for ループでは、qbpp::Vector<qbpp::Vector<qbpp::Expr>> オブジェクト f に含まれる各 qbpp::Vector<qbpp::Expr> オブジェクトが順に vec に参照されます。内側の for ループでは、vec に含まれる各 qbpp::Expr オブジェクトが順に element に参照されます。

このプログラムは以下を出力します:

(-1 +2*x[0][0])(-1 +2*x[0][1])(-1 +2*x[0][2])
(-1 +2*x[1][0])(-1 +2*x[1][1])(-1 +2*x[1][2])

サブ配列アクセスと操作

多次元配列から行や列などのサブ配列を取得するには、タプルインデックス(Array::operator())を使います。 任意の軸に沿ったスライスや連結については スライス関数と連結関数 を参照してください。

PyQBPP は、多次元の変数(Var オブジェクト)および多次元の整数変数(VarInt オブジェクト)を、任意の次元数で定義できます。

それぞれ var() および var_int() を使用します。

基本的な使用方法は次のとおりです。

  • var("name", s1, s2, ..., sd)
    指定した名前nameと形状 \(s1 \times s2 \times \cdots \times sd\) を持つ Var オブジェクトの多次元配列を作成します。

  • between(var_int("name", s1, s2, ..., sd), l, u)
    指定した範囲 [l, u] と形状 \(s1 \times s2 \times \cdots \times sd\) を持つ VarInt オブジェクトの多次元配列を作成します。

次の PyQBPP プログラムは、次元 \(2 \times 3 \times 4\) の2値変数と整数変数をそれぞれ作成します。

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

x = qbpp.var("x", 2, 3, 4)
y = qbpp.between(qbpp.var_int("y", 2, 3, 4), 1, 8)
print("x =", x)
print("y =", y)

このプログラムは次の出力を生成します。

x : {{{x[0][0][0],x[0][0][1],x[0][0][2],x[0][0][3]},{x[0][1][0],x[0][1][1],x[0][1][2],x[0][1][3]},{x[0][2][0],x[0][2][1],x[0][2][2],x[0][2][3]}},{{x[1][0][0],x[1][0][1],x[1][0][2],x[1][0][3]},{x[1][1][0],x[1][1][1],x[1][1][2],x[1][1][3]},{x[1][2][0],x[1][2][1],x[1][2][2],x[1][2][3]}}}
y : {{{1 +y[0][0][0][0] +2*y[0][0][0][1] +4*y[0][0][0][2], ... }}}

x の各 Var オブジェクトは x[i][j][k] のようにアクセスできます。

y の各 VarInt オブジェクトも y[i][j][k] のようにアクセスできますが、内部的には次の3つの2値変数で表現されています。

y[i][j][k][0]
y[i][j][k][1]
y[i][j][k][2]

これらは、指定された整数範囲を2進数でエンコードしたものに対応します。

配列のプロパティ

PyQBPP の配列は NumPy と同様のプロパティを提供します。

プロパティ

戻り値

説明

a.shape

タプル

各次元のサイズ(例: (2, 3, 4)

a.ndim

int

次元数

a.size

int

全要素数(各次元のサイズの積)

len(a)

int

最外次元のサイズ(a.shape[0] と同じ)

次のプログラムは出力を確かめるものです:

multi-dimensional-variables-and-expressions-program7.py
import pyqbpp as qbpp

x = qbpp.var("x", shape=(2, 3, 4))
print("shape:", x.shape)   # (2, 3, 4)
print("ndim:", x.ndim)     # 3
print("size:", x.size)     # 24
print("len:", len(x))      # 2

定数・変数・式の配列

Python リストを qbpp.array(リスト) に渡すと、先頭要素の型から要素型が自動判別されて配列が作成されます:

呼び出し形式

結果

説明

qbpp.array([1, 2, 3])

1次元の整数定数の配列

整数定数の配列

qbpp.array([[1,2],[3,4]])

2次元の整数定数の配列

2次元の整数定数配列

qbpp.array([v1, v2])

1次元のバイナリ変数の配列

バイナリ変数の配列

qbpp.array([e1, e2])

1次元の式の配列

式の配列

整数定数配列は変数配列との要素ごとの演算に使用できます。以下のプログラムは、\(2 \times 2\) の整数定数行列 c とバイナリ変数行列 x の要素ごとの積を合計します:

multi-dimensional-variables-and-expressions-program8.py
import pyqbpp as qbpp

c = qbpp.array([[1, 2], [3, 4]])
x = qbpp.var("x", shape=(2, 2))
f = qbpp.sum(c * x)
print("f =", f)

c * x は要素ごとの積を返し、qbpp.sum がその全要素を合計して単一の式を返します。このプログラムの出力は以下の通りです:

f = x[0][0] +2*x[0][1] +3*x[1][0] +4*x[1][1]

個別の範囲を持つ整数変数配列の作成

多次元整数変数配列を定義する場合、qbpp.var("name", shape=(...), between=(l, u)) で作成された全要素は同じ範囲 \([l, u]\) を共有します。 しかし実際の問題では、各要素に異なる範囲が必要な場合が多くあります。これを実現するには2つの方法があります。

方法1: プレースホルダ配列

まず qbpp.var("name", shape=..., equal=val)プレースホルダ配列を作成し、ループ内で各要素に個別の範囲を割り当てます:

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

max_vals = qbpp.array([3, 7, 15, 5])
x = qbpp.var("x", shape=len(max_vals), equal=0)
for i in range(len(max_vals)):
    x[i] = qbpp.constrain(x[i], between=(0, max_vals[i]))
for i in range(len(max_vals)):
    print(f"x[{i}] = {x[i]}")

このプログラムでは、qbpp.var("x", shape=4, equal=0) がプレースホルダとして定数 0 の VarInt オブジェクト4個の配列を作成します。 各要素は qbpp.constrain(x[i], between=(0, max_vals[i])) で個別の範囲に再代入されます。qbpp.constrain() はプレースホルダから名前を自動的に引き継ぐため、明示的な名前の指定は不要です。

注釈 equal= の値は0以外の任意の整数を指定できます。この構文はメモリ上に可変配列を確保し、各要素を個別に再代入できるようにします。

方法2: between= にリストを渡す

between の境界値にPythonリストを渡すことができます。 配列の各要素にリストの対応する範囲が割り当てられます:

multi-dimensional-variables-and-expressions-program9.py
import pyqbpp as qbpp

max_vals = [3, 7, 15, 5]
x = qbpp.var("x", shape=len(max_vals), between=(0, max_vals))
for i in range(len(max_vals)):
    print(f"x[{i}] = {x[i]}")

これが最も簡潔な方法です。shape= で配列の次元を指定し、 between= がリストから要素ごとに個別の範囲を割り当てます。

方法3: リスト内包表記と array

Python のリスト内包表記を qbpp.array() で包む方法もあります:

multi-dimensional-variables-and-expressions-program3.py
import pyqbpp as qbpp

max_vals = qbpp.array([3, 7, 15, 5])
x = qbpp.array([qbpp.var(f"x[{i}]", between=(0, max_vals[i]))
                  for i in range(len(max_vals))])

この方法ではプレースホルダなしに変数を直接作成します。 各変数に明示的な名前(例: f"x[{i}]")を指定する必要があることと、 要素ごとの演算を使用するには結果を qbpp.array() で包む必要がある点に注意してください。`

多次元式の定義

PyQBPP では、expr() を使用して任意の深さの多次元式(Expr オブジェクト)を定義できます。

  • expr(shape=(s1, s2, ..., sd))
    形状 \(s1 \times s2 \times \cdots \times sd\) を持つ Expr の多次元配列を作成します。

次のプログラムでは、形状 \(2 \times 3 \times 4\) の3次元変数配列 x と、サイズ \(2 \times 3\) の2次元式配列 f を定義しています。

三重ループを用いて、各 f[i][j]
x[i][j][0], x[i][j][1], x[i][j][2], x[i][j][3] の和を加算しています。

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

x = qbpp.var("x", shape=(2, 3, 4))
f = qbpp.expr(shape=(2, 3))
for i in range(2):
    for j in range(3):
        for k in range(4):
            f[i][j] += x[i][j][k]
f.simplify_as_binary()

for i in range(2):
    for j in range(3):
        print(f"f[{i}][{j}] =", f[i][j])

simplify_as_binary() は、多次元の Expr 配列にも適用できます。
この場合、各要素に対して 要素ごと(element-wise)simplify_as_binary() が適用されます。

このプログラムは次の出力を生成します。

f = {{x[0][0][0] +x[0][0][1] +x[0][0][2] +x[0][0][3],x[0][1][0] +x[0][1][1] +x[0][1][2] +x[0][1][3],x[0][2][0] +x[0][2][1] +x[0][2][2] +x[0][2][3]},{x[1][0][0] +x[1][0][1] +x[1][0][2] +x[1][0][3],x[1][1][0] +x[1][1][1] +x[1][1][2] +x[1][1][3],x[1][2][0] +x[1][2][1] +x[1][2][2] +x[1][2][3]}}
f[0][0] = x[0][0][0] +x[0][0][1] +x[0][0][2] +x[0][0][3]
f[0][1] = x[0][1][0] +x[0][1][1] +x[0][1][2] +x[0][1][3]
f[0][2] = x[0][2][0] +x[0][2][1] +x[0][2][2] +x[0][2][3]
f[1][0] = x[1][0][0] +x[1][0][1] +x[1][0][2] +x[1][0][3]
f[1][1] = x[1][1][0] +x[1][1][1] +x[1][1][2] +x[1][1][3]
f[1][2] = x[1][2][0] +x[1][2][1] +x[1][2][2] +x[1][2][3]

演算による式配列の作成

Expr オブジェクトの配列は、明示的に expr() を呼び出さなくても作成できます。
算術演算が配列形状の結果を生成する場合、同じ形状の Expr オブジェクトの配列が自動的に作成されます。

multi-dimensional-variables-and-expressions-program5.py
import pyqbpp as qbpp

x = qbpp.var("x", shape=(2, 3))
f = x + 1
f += x - 2
f.simplify_as_binary()
for i in range(2):
    for j in range(3):
        print(f"f[{i}][{j}] =", f[i][j])

このプログラムの出力は次のとおりです。

f[0][0] = -1 +2*x[0][0]
f[0][1] = -1 +2*x[0][1]
f[0][2] = -1 +2*x[0][2]
f[1][0] = -1 +2*x[1][0]
f[1][1] = -1 +2*x[1][1]
f[1][2] = -1 +2*x[1][2]

多次元配列のイテレーション

PyQBPPのベクトルはPythonのイテレーションをサポートしているため、ネストされた for ループが使用できます。

multi-dimensional-variables-and-expressions-program6.py
import pyqbpp as qbpp

x = qbpp.var("x", shape=(2, 3))
f = x + 1
f += x - 2
f.simplify_as_binary()
for row in f:
    for element in row:
        print(f"({element})", end="")
    print()

このプログラムは以下を出力します:

(-1 +2*x[0][0])(-1 +2*x[0][1])(-1 +2*x[0][2])
(-1 +2*x[1][0])(-1 +2*x[1][1])(-1 +2*x[1][2])

array と Python の list

PyQBPP の array は Hi-QUBO 共有ライブラリ(.so)に裏打ちされた不透明オブジェクトです。 Python の list とは異なり、Hi-QUBO の演算に最適化された専用のデータ構造です。

Python list から array の作成

qbpp.array() を使って Python リストを array に変換できます:

w = qbpp.array([64, 27, 47, 74, 12, 83, 63, 40])

変換後の array は、要素ごとの算術演算(+, -, *, /, ~)、sum()sqr()simplify() などの Hi-QUBO 関数を効率的にサポートします。

qbpp.array() が不要な場合

Python リストが array との算術演算で使われる場合、自動的に変換されます。 例えば:

w = [64, 27, 47, 74, 12, 83, 63, 40]
x = qbpp.var("x", shape=len(w))
f = w * x       # list * Array → 要素ごとの乗算

この場合、wqbpp.array() でラップする必要はありません。 ただし、w が複数の演算で繰り返し使われる場合は、あらかじめ qbpp.array() でラップしておくことで、list から array への変換が毎回発生するのを避け、高速化が期待できます。

例: list と array の動作の違い

以下の例は、Python の list と array の違いを示しています:

multi-dimensional-variables-and-expressions-program7.py
import pyqbpp as qbpp

x = qbpp.var("x", shape=(2, 3, 4))
print("shape:", x.shape)   # (2, 3, 4)
print("ndim:", x.ndim)     # 3
print("size:", x.size)     # 24
print("len:", len(x))      # 2

出力は以下の通りです:

Python の list である u では、2 * u はリストの繰り返し(8要素)になります。 array である w では、2 * w は要素ごとの乗算(各要素が2倍)になります。

Python list との主な違い

array

Python list

要素ごとの +

要素ごとの加算

リストの連結

要素ごとの *

要素ごとの乗算

リストの繰り返し

~x

要素ごとの否定

TypeError

sum()

全要素の合計を式として返す

Python 組み込みの sum

sqr()

要素ごとの二乗

利用不可

append(), pop()

利用不可

利用可能

スライス

x[1:3], x[:n], x[-n:]

x[1:3]

注釈: array は固定サイズの不透明コンテナです。append()pop()insert()、スライス代入などの Python リスト操作はサポートされていません。 部分配列の抽出には Python スライス構文(x[1:3]x[:n] 等)を使用してください。