式の評価

qbpp::MapList を使用した評価

式の値は、各変数とその値の組(ペア)のリストとして代入を与えることで簡単に計算できます。
このリストは qbpp::MapList オブジェクトとして定義できます。

例えば、次のプログラムは、\((x,y,z)=(0,1,1)\) に対して関数 \(f(x,y,z)=x+2y+3z-3\) の値を計算します。

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

int main() {
  auto x = qbpp::var("x");
  auto y = qbpp::var("y");
  auto z = qbpp::var("z");
  auto f = qbpp::sqr(x + 2 * y + 3 * z - 3);

  qbpp::MapList ml;
  ml.push_back({x, 0});
  ml.push_back({y, 1});
  ml.push_back({z, 1});
  
  std::cout << "assignment =" << ml << std::endl;
  std::cout << "f(0,1,1) = " << f(ml) << std::endl;
}

このプログラムでは、qbpp::MapList オブジェクト ml を定義し、{x, 0}{y, 1}{z, 1} という代入を ml に追加しています。

その後、f(ml) を呼び出すことで、\(f(0,1,1)\) の値が計算されます。
このプログラムの出力は次の通りです。

assignment = {{x,0},{y,1},{z,1}}
f(0,1,1) = 4

また、次のように直接代入することもできます。

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

int main() {
  auto x = qbpp::var("x");
  auto y = qbpp::var("y");
  auto z = qbpp::var("z");
  auto f = qbpp::sqr(x + 2 * y + 3 * z - 3);

  std::cout << "f(0,1,1) = " << f({{x, 0}, {y, 1}, {z, 1}}) << std::endl;
}

qbpp::Sol を使用した評価

解のオブジェクト(qbpp::Sol)は、式のオブジェクト(qbpp::Expr)の値を評価するために使用できます。これを行うには、まず与えられた式 f に対応する qbpp::Sol オブジェクト sol を作成します。 新しく作成された qbpp::Sol オブジェクトは、すべての変数に 0 を代入することで初期化されます。

qbpp::Sol のメンバ関数 set() を使用すると、個々の変数に値を割り当てることができます。その後、f(sol)sol(f) のどちらも、sol に保存されている代入のもとでの式 f の値を返します。

さらに、メンバ関数 comp_energy() も同じ値を計算して返します。

evaluating-expressions-program3.cpp
#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x");
  auto y = qbpp::var("y");
  auto z = qbpp::var("z");
  auto f = qbpp::sqr(x + 2 * y + 3 * z - 3);

  qbpp::Sol sol(f);
  sol.set(y, 1);
  sol.set(z, 1);
  
  std::cout << "f(0,1,1) = " << f(sol) << std::endl;
  std::cout << "f(0,1,1) = " << sol(f) << std::endl;
  std::cout << "f(0,1,1) = " << sol.comp_energy() << std::endl;
}

sol というqbpp::Solオブジェクトのメンバ関数 comp_energy() は、エネルギー値を計算して内部にキャッシュします。

また、ソルバーによって返されたqbpp::Solオブジェクトには、すでにエネルギー値が計算されてキャッシュされています。

そのため、再計算せずにエネルギー値を取得したい場合は、次のようにメンバ関数 energy() を使用します。

evaluating-expressions-program4.cpp
#include <qbpp/qbpp.hpp>
#include <qbpp/easy_solver.hpp>

int main() {
  auto x = qbpp::var("x");
  auto y = qbpp::var("y");
  auto z = qbpp::var("z");
  auto f = qbpp::sqr(x + 2 * y + 3 * z - 4);

  f.simplify_as_binary();
  auto solver = qbpp::EasySolver(f);
  auto sol = solver.search({{"target_energy", 0}});

  std::cout << "sol = " << sol << std::endl;
  std::cout << "energy = " << sol.energy() << std::endl;

  sol.flip(z);
  std::cout << "flipped sol = " << sol << std::endl;
  std::cout << "flipped energy = " << sol.energy() << std::endl;
}

このプログラムでは、sol.energy() は正しく 0 を返します。
しかし、変数 z を反転(flip)した後は、キャッシュされているエネルギー値が無効になります。

そのため、エネルギーを再計算せずに sol.energy() を呼び出すと、次のように**実行時エラー(runtime error)**が発生します。

sol = 0:{{x,1},{y,0},{z,1}}
energy = 0
terminate called after throwing an instance of 'std::runtime_error'

この問題を解決するには、解を変更した後に sol.comp_energy() を呼び出してエネルギーを明示的に再計算する必要があります。
次のように記述します。

  std::cout << "sol = " << sol << std::endl;
  std::cout << "energy = " << sol.energy() << std::endl;
  
  sol.flip(z);
  std::cout << "sol.comp_energy() = " << sol.comp_energy() << std::endl;
  std::cout << "flipped sol = " << sol << std::endl;
  std::cout << "flipped energy = " << sol.energy() << std::endl;

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

sol = 0:{{x,1},{y,0},{z,1}}
energy = 0
sol.comp_energy() = 9
flipped sol = 9:{{x,1},{y,0},{z,0}}
flipped energy = 9

辞書を使った評価

式の値は、すべての変数への値の割り当てを {変数: 値} の辞書として与えることで簡単に計算できます。 辞書は完全な割り当てを構成する (変数, 値) のペアのリストを保持します。

例えば、次のプログラムは、\((x,y,z)=(0,1,1)\) に対して関数 \(f(x,y,z)=x+2y+3z-3\) の値を計算します。

evaluating-expressions-program1.py
import pyqbpp as qbpp

x = qbpp.var("x")
y = qbpp.var("y")
z = qbpp.var("z")
f = qbpp.sqr(x + 2 * y + 3 * z - 3)

ml = {x: 0, y: 1, z: 1}

print("assignment =", ml)
print("f(0,1,1) =", f(ml))

このプログラムでは、ペアのリスト ml = {x: 0, y: 1, z: 1}\(x=0\), \(y=1\), \(z=1\) の割り当てを定義しています。そして f(...)\(f(0,1,1)\) の値を返します。このプログラムの出力は以下の通りです:

assignment = {{x,0},{y,1},{z,1}}
f(0,1,1) = 4

あるいは、辞書リテラルとして直接、または (変数, 値) のタプルのリストとして 割り当てを与えることもできます:

evaluating-expressions-program2.py
import pyqbpp as qbpp

x = qbpp.var("x")
y = qbpp.var("y")
z = qbpp.var("z")
f = qbpp.sqr(x + 2 * y + 3 * z - 3)

print("f(0,1,1) =", f({x: 0, y: 1, z: 1}))
print("f(0,1,1) =", f([(x, 0), (y, 1), (z, 1)]))

Sol を使用した評価

解のオブジェクト(Sol)は、式のオブジェクト(Expr)の値を評価するために使用できます。
そのためには、まず与えられた式に関連付けた Sol オブジェクトを構築します。 新しく作成された Sol オブジェクトはすべてゼロの割り当てで初期化されます。

sol.set(x, value) メソッドを使って、個々の変数に値を割り当てることができます。 そして、f(sol)sol(f) のどちらも、sol に格納された割り当ての下での 式 f の値を返します。 さらに、comp_energy() メソッドも同じ値を計算して返します。

evaluating-expressions-program3.py
import pyqbpp as qbpp

x = qbpp.var("x")
y = qbpp.var("y")
z = qbpp.var("z")
f = qbpp.sqr(x + 2 * y + 3 * z - 3)
f.simplify_as_binary()

sol = qbpp.Sol(f)
sol.set(y, 1)
sol.set(z, 1)

print("f(0,1,1) =", f(sol))
print("f(0,1,1) =", sol(f))
print("f(0,1,1) =", sol.comp_energy())

sol のメソッド comp_energy() は、エネルギー値を計算して内部にキャッシュします。また、ソルバーによって返されたSolオブジェクトには、すでにエネルギー値が計算されてキャッシュされています。

そのため、再計算せずにエネルギー値を取得したい場合は、次のようにメンバ関数 energy() を使用できます。

evaluating-expressions-program4.py
import pyqbpp as qbpp

x = qbpp.var("x")
y = qbpp.var("y")
z = qbpp.var("z")
f = qbpp.sqr(x + 2 * y + 3 * z - 4)
f.simplify_as_binary()

solver = qbpp.EasySolver(f)
sol = solver.search(target_energy=0)

print("sol =", sol)
print("energy =", sol.energy)

sol.flip(z)
print("flipped sol =", sol)
print("flipped energy =", sol.energy)

このプログラムの出力は以下の通りです:

sol = 0:{{x,1},{y,0},{z,1}}
energy = 0
RuntimeError: energy is not up to date; call comp_energy() after modifying the solution

解を変更した後(例えば flip() を使った後)、キャッシュされたエネルギーは無効になります。 comp_energy() を呼び出して明示的に再計算する必要があります:

print("sol =", sol)
print("energy =", sol.energy)

sol.flip(z)
print("sol.comp_energy() =", sol.comp_energy())
print("flipped sol =", sol)
print("flipped energy =", sol.energy)

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

sol = 0:{{x,1},{y,0},{z,1}}
energy = 0
sol.comp_energy() = 9
flipped sol = 9:{{x,1},{y,0},{z,0}}
flipped energy = 9

comp_energy() を呼び出した後は、sol.energy プロパティも正しい(再計算後の) エネルギーを返します。