Evaluating Expressions
Evaluation using qbpp::MapList
The value of expressions can be simply done by providing an assignment of values to all elements as a list of pairs of a variable and its value. A list can be defined as a qbpp::MapList object. For example, the following program computes the function $f(x,y,z)$ for $(x,y,z)=(0,1,1)$.
#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;
}
In this program, qbpp::MapList object ml is defined, and an assignment {x, 0}, {y, 1} and {z, 1} is appended to ml. Then f(ml) returns the value of $f(0,1,1)$. This program displays the follwing output:
{x: 0, y: 1, z: 1}
f(0,1,1) = 4
Alternratively, we can provide an assignemt directly as follows:
#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;
}
Native integer variables (qbpp::int_var()) can also be used as keys. The value must lie within the declared range; an out-of-range value throws std::invalid_argument. See Native integer variables for an example.
Expressions with constraints (qbpp::cons()) or with qbpp::abs, qbpp::relu, qbpp::max, and qbpp::min are evaluated the same way. The value is the same as with a qbpp::Sol: the objective plus the value of each constraint (weight × violation squared). Every variable that the expression uses must be assigned, including variables that appear only inside a constraint. A missing variable throws std::invalid_argument.
Evaluation using qbpp::Sol
A solution object (qbpp::Sol) can be used to evaluate the value of an expression (qbpp::Expr). To do this, we first construct a qbpp::Sol object sol associated with a given expression f. The newly created qbpp::Sol object is initialized with the all-zero assignment.
Using the set() member function of qbpp::Sol, we can assign values to individual variables. Then, both f(sol) and sol(f) return the value of the expression f under the assignment stored in sol. Furthermore, the comp_energy() member function computes and returns the same value.
#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;
}
Note that the member function comp_energy() of a solution object sol computes the energy value and caches it internally. In addition, a solution object returned by a solver already has its energy value computed and cached. To retrieve the energy without recomputing it, you can use the member function energy(), as shown below:
#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;
}
In this program, sol.energy() correctly returns 0. However, after flipping the variable z, the cached energy value becomes invalid. Printing via std::cout << sol still succeeds, because it computes the energy of the current assignment on the fly for display (the cache stays invalid). Calling sol.energy() while the cache is invalid, however, results in a runtime error and the program aborts, as shown below (in this example it occurs at the last print):
sol = 0:{{x,1},{y,0},{z,1}}
energy = 0
flipped sol = 9:{{x,1},{y,0},{z,0}}
flipped energy = qbpp: Sol::energy(): energy is invalid (variable changed after last computation). Call comp_energy() first.
To resolve this issue, you must explicitly recompute the energy by calling sol.comp_energy() after modifying the solution, as follows:
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;
This program produces the following output:
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