式の評価

qbpp::MapListを使用した評価

式の値は,変数とその値のペアのリストとして全要素への値の割り当てを提供することで簡単に計算できます. リストは qbpp::MapList オブジェクトとして定義できます. 例えば,以下のプログラムは \((x,y,z)=(0,1,1)\) に対して関数 \(f(x,y,z)\) を計算します.

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 << 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)\) の値を返します. このプログラムは以下の出力を表示します:

{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 オブジェクトは全てゼロの割り当てで初期化されます.

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 のメンバ関数 comp_energy() はエネルギー値を計算し,内部にキャッシュすることに注意してください. また,ソルバーが返す解オブジェクトは,既にエネルギー値が計算されキャッシュされています. 再計算せずにエネルギーを取得するには,以下に示すようにメンバ関数 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 をフリップした後,キャッシュされたエネルギー値は無効になります. キャッシュが無効な状態では,sol.energy() の呼び出しだけでなく std::cout << sol による表示もエネルギーを読むため,以下のようにランタイムエラーが発生します(この例ではフリップ直後の出力行で発生):

sol = 0:{{x,1},{y,0},{z,1}}
energy = 0
qbpp: Sol::energy(): energy is invalid (variable changed after last computation). Call comp_energy() first.

この問題を解決するには,解を変更した後に 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)\) を計算します:

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(ml) が \(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)を使って式の値を評価することもできます. そのためには,まず与えられた式 f に関連付けた解 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() はエネルギー値を計算し, 解の内部にキャッシュすることに注意してください. また,ソルバーが返す解は,既にエネルギー値が計算されキャッシュされています. 再計算せずにエネルギーを取得するには,以下に示すように 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.energy は正しく 0 を返します. しかし,変数 z をフリップした後,キャッシュされたエネルギー値は無効になります. キャッシュが無効な状態では,sol.energy へのアクセスだけでなく print(sol) による表示も エネルギーを読むため,以下のようにランタイムエラーが発生します(この例ではフリップ直後の 出力行で発生):

sol = Sol(energy=0, {x: 1, y: 0, z: 1})
energy = 0
RuntimeError: Sol.energy: energy is invalid (variable changed after last computation). Call comp_energy() first.

この問題を解決するには,解を変更した後に sol.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 = Sol(energy=0, {x: 1, y: 0, z: 1})
energy = 0
sol.comp_energy() = 9
flipped sol = Sol(energy=9, {x: 1, y: 0, z: 0})
flipped energy = 9

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