ベクトルに対する基本演算子と関数

基本的に、ベクトルに対する演算子や関数は**要素ごと(element-wise)**に適用されます。

ベクトルに対する基本演算子

基本演算子 +, -, *, / は、変数ベクトルおよび式ベクトルに対して使用できます。

QUBO++ では、これらの演算子は各要素に対して個別に適用されます。

PyQBPP では、これらの演算子は各要素に対して個別に適用されます。

ベクトルとスカラーの演算

ベクトルとスカラーを組み合わせる場合、スカラーはベクトルの各要素に適用されます。

例えば、サイズ3のベクトル x に対して:

  • 2 * x{2*x[0], 2*x[1], 2*x[2]} を生成します。

  • x + 1{x[0] + 1, x[1] + 1, x[2] + 1} を生成します。

次のプログラムは、この動作を示しています。

vector-operators-and-functions-program1.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x", 3);
  auto f = 2 * x + 1;

  std::cout << "f = " << f << std::endl;
  for (size_t i = 0; i < f.size(); ++i) {
    std::cout << "f[" << i << "] = " << f[i] << std::endl;
  }
}
vector-operators-and-functions-program1.py
import pyqbpp as qbpp

x = qbpp.var("x", 3)
f = 2 * x + 1

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

このプログラムは、2値変数のベクトル x = {x[0], x[1], x[2]} を生成します。2 * x は各要素を2倍し、+ 1 は各要素に1を加えるため、f{1 + 2*x[0], 1 + 2*x[1], 1 + 2*x[2]}になります。 このプログラムの出力は次のとおりです。

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

ベクトル同士の演算

同じサイズの2つのベクトルを組み合わせる場合、演算は各インデックスごとに要素単位で実行されます。

次の例では、サイズ3のベクトル xy を使用しています。

vector-operators-and-functions-program2.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x", 3);
  auto y = qbpp::var("y", 3);
  auto f = 2 * x + 3 * y + 1;

  std::cout << "f = " << f << std::endl;
  for (size_t i = 0; i < f.size(); ++i) {
    std::cout << "f[" << i << "] = " << f[i] << std::endl;
  }
}
vector-operators-and-functions-program2.py
import pyqbpp as qbpp

x = qbpp.var("x", 3)
y = qbpp.var("y", 3)
f = 2 * x + 3 * y + 1

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

ここで

  • 2 * x{2*x[0], 2*x[1], 2*x[2]} になります。

  • 3 * y{3*y[0], 3*y[1], 3*y[2]} になります。

  • それらの加算は要素ごとに行われるため、i 番目の要素は 2*x[i] + 3*y[i] になります。

  • + 1 も再び要素ごとに適用されます。

したがって、f は次のようになります。

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

出力は次のとおりです。

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

ベクトル同士の演算では、同じサイズである必要があります

より複雑な要素ごとの演算例

次の例では、ベクトルとスカラーの演算、ベクトル同士の演算、単項マイナス、および括弧を含む、より複雑な式を示します。

vector-operators-and-functions-program3.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x", 3);
  auto y = qbpp::var("y", 3);
  auto f = 6 * -(x + 1) * (y - 1);
  auto g = f / 3;

  std::cout << "f = " << f << std::endl;
  for (size_t i = 0; i < x.size(); ++i) {
    std::cout << "f[" << i << "] = " << f[i] << std::endl;
  }

  std::cout << "g = " << g << std::endl;
  for (size_t i = 0; i < x.size(); ++i) {
    std::cout << "g[" << i << "] = " << g[i] << std::endl;
  }
}
vector-operators-and-functions-program3.py
import pyqbpp as qbpp

x = qbpp.var("x", 3)
y = qbpp.var("y", 3)
f = 6 * -(x + 1) * (y - 1)
g = f / 3

print("f = ", f)
for i in range(len(x)):
    print(f"f[{i}] = {f[i]}")

print("g = ", g)
for i in range(len(x)):
    print(f"g[{i}] = {g[i]}")

この例でも、すべての演算は要素ごとに適用されます。

  • x + 1 および y - 1 は、それぞれ {x[i] + 1}{y[i] - 1} を生成します。

  • 単項マイナス -(x + 1) も要素ごとに適用され、{-(x[i] + 1)} になります。

  • 乗算 6 * -(x + 1) * (y - 1) も要素ごとに実行されます。

したがって、各インデックス i に対して:

f[i] = 6 * (-(x[i] + 1)) * (y[i] - 1)

これを展開すると:

f[i] = 6 -6*x[i]*y[i] +6*x[i] -6*y[i]

最後に、g = f / 3 は各要素を 3 で割るため、

g[i] = 2 -2*x[i]*y[i] +2*x[i] -2*y[i]

となります。

出力は次のとおりです。

f = {6 -6*x[0]*y[0] +6*x[0] -6*y[0],6 -6*x[1]*y[1] +6*x[1] -6*y[1],6 -6*x[2]*y[2] +6*x[2] -6*y[2]}
f[0] = 6 -6*x[0]*y[0] +6*x[0] -6*y[0]
f[1] = 6 -6*x[1]*y[1] +6*x[1] -6*y[1]
f[2] = 6 -6*x[2]*y[2] +6*x[2] -6*y[2]
g = {2 -2*x[0]*y[0] +2*x[0] -2*y[0],2 -2*x[1]*y[1] +2*x[1] -2*y[1],2 -2*x[2]*y[2] +2*x[2] -2*y[2]}
g[0] = 2 -2*x[0]*y[0] +2*x[0] -2*y[0]
g[1] = 2 -2*x[1]*y[1] +2*x[1] -2*y[1]
g[2] = 2 -2*x[2]*y[2] +2*x[2] -2*y[2]

ベクトルに対する複合演算子

+=, -=, *=, /= といった複合演算子も、変数ベクトルおよび式ベクトルに対して使用できます。
これらの演算も 要素ごと(element-wise) に適用されます。

次の例は、サイズ3のベクトルに対する複合演算子の動作を示しています。

vector-operators-and-functions-program4.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x", 3);
  auto y = qbpp::var("y", 3);
  auto f = 6 * x + 4;

  f += 3 * y;
  std::cout << "f = " << f << std::endl;

  f -= 12;
  std::cout << "f = " << f << std::endl;

  f *= 2 * y;
  std::cout << "f = " << f << std::endl;

  f /= 2;
  std::cout << "f = " << f << std::endl;
}
vector-operators-and-functions-program4.py
import pyqbpp as qbpp

x = qbpp.var("x", 3)
y = qbpp.var("y", 3)
f = 6 * x + 4

f += 3 * y
print("f =", f)
f -= 12
print("f =", f)
f *= 2 * y
print("f =", f)
f /= 2
print("f =", f)

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

f = {4 +6*x[0] +3*y[0],4 +6*x[1] +3*y[1],4 +6*x[2] +3*y[2]}
f = {-8 +6*x[0] +3*y[0],-8 +6*x[1] +3*y[1],-8 +6*x[2] +3*y[2]}
f = {12*x[0]*y[0] +6*y[0]*y[0] -16*y[0],12*x[1]*y[1] +6*y[1]*y[1] -16*y[1],12*x[2]*y[2] +6*y[2]*y[2] -16*y[2]}
f = {6*x[0]*y[0] +3*y[0]*y[0] -8*y[0],6*x[1]*y[1] +3*y[1]*y[1] -8*y[1],6*x[2]*y[2] +3*y[2]*y[2] -8*y[2]}

ベクトルに対する square 関数

sqr 関数もベクトルに対して使用できます。
こちらも各要素ごとに二乗が計算されます。

vector-operators-and-functions-program5.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x", 3);
  auto f = x + 1;

  std::cout << "f = " << qbpp::sqr(f) << std::endl;
  std::cout << "f = " << f << std::endl;

  f.sqr();
  std::cout << "f = " << f << std::endl;
}
vector-operators-and-functions-program5.py
import pyqbpp as qbpp

x = qbpp.var("x", 3)
f = qbpp.sqr(x + 1)

print("f =", f)

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

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

ベクトルに対する simplify 関数

simplify 関数もベクトルに対して利用可能で、各要素がそれぞれ簡約されます。

vector-operators-and-functions-program6.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x", 3);
  auto f = qbpp::sqr(x - 1);

  std::cout << "f = " << f << std::endl;
  std::cout << "simplified(f) = " << qbpp::simplify(f) << std::endl;
  std::cout << "simplified_as_binary(f) = " << qbpp::simplify_as_binary(f) << std::endl;
  std::cout << "simplified_as_spin(f) = " << qbpp::simplify_as_spin(f) << std::endl;
}
vector-operators-and-functions-program6.py
import pyqbpp as qbpp

x = qbpp.var("x", 3)
f = qbpp.sqr(x - 1)
print("f =", f)
print("qbpp.simplify(f) =", qbpp.simplify(f))
print("qbpp.simplify_as_binary(f) =", qbpp.simplify_as_binary(f))
print("qbpp.simplify_as_spin(f) =", qbpp.simplify_as_spin(f))

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

f = {1 +x[0]*x[0] -x[0] -x[0],1 +x[1]*x[1] -x[1] -x[1],1 +x[2]*x[2] -x[2] -x[2]}
simplified(f) = {1 -2*x[0] +x[0]*x[0],1 -2*x[1] +x[1]*x[1],1 -2*x[2] +x[2]*x[2]}
simplified_as_binary(f) = {1 -x[0],1 -x[1],1 -x[2]}
simplified_as_spin(f) = {2 -2*x[0],2 -2*x[1],2 -2*x[2]}

NOTE:
これらの演算子および関数は、多次元配列に対しても同様に動作します。