2. The cost¶
The search needs a way to compare tours. We start with tour length, then combine it with other measures without changing the solution or move code.
Cost components¶
Costs are minimized by default. To maximize a profit, use cost::apply to
return its negation or the lost profit. Cost semantics
in the next chapter also explains how to define a custom comparison.
A cost component computes one term of the objective:
class TourLength : public easylocal::input_base<Tsp>
{
public:
using input_base::input_base; // constructed from the Input, read by input()
double evaluate(const Tour& tour) const
{
const auto n = tour.order.size();
double length = 0.0;
for (std::size_t k = 0; k < n; ++k)
{
const auto from = tour.order[k];
const auto to =
tour.order[(k + 1) % n]; // the last city goes back to the first
length += input().distance[from][to];
}
return length;
}
};
- The only requirement is
evaluate(const Solution&) const -> Value. Valueis usually a number, as here. It may also be a struct of your own, a domain value: an advanced use, described in Domain values.input_base<Tsp>suppliesinput()and the constructor inherited byusing input_base::input_base;. The SolutionManager in chapter 1 uses the same base. You can also provideinput()yourself or omit it if the component needs no Input (see the convenience bases).- Binding the runner constructs the component as
Component{const Input&, args...}, orComponent{args...}for a component that needs no Input. Precompute instance data in this constructor so thatevaluateonly needs the solution. For example:C(const Tsp& input) : input_base{input}, table_{build(input)} {}. (k + 1) % nmakes the last city connect back to the first one.
Components are attached to the SolutionManager with a recipe
(el is the alias of the namespace easylocal, see the
conventions):
auto sm = el::solution_manager<TourManager>() | el::component<TourLength>();
This recipe describes a TourManager with TourLength as its cost. Binding
it to an Input will construct the services. The named spelling is
solution_manager<TourManager>().with_cost(component<TourLength>()).
Cost expressions¶
With several components, a cost expression combines their values. For example, this weighted sum balances tour length against the longest edge:
auto weighted_sm = el::solution_manager<TourManager>()
| el::cost::sum(el::component<TourLength>(), el::component<MaxEdge>() * 10.0);
MaxEdge is a second component, the longest edge of the tour:
class MaxEdge : public easylocal::input_base<Tsp>
{
public:
using input_base::input_base;
double evaluate(const Tour& tour) const
{
const auto n = tour.order.size();
double longest = 0.0;
for (std::size_t k = 0; k < n; ++k)
{
const auto from = tour.order[k];
const auto to = tour.order[(k + 1) % n];
longest = std::max(longest, input().distance[from][to]);
}
return longest;
}
};
child * wis shorthand forcost::weighted(child, w); either form marks a term of acost::sum, and only there. The weights are parameters, herecost.weights(see Configuration).cost::sumadds numbers, andcost::apply(f, c1, ..., cn)computes anything else from the values of its children:cost::apply([](double longest) { return std::max(0.0, longest - 8.0); }, component<MaxEdge>())is the excess of the longest edge over 8.- Expressions can contain components or other expressions. This keeps the cost structure and weights together, so you can change the objective while reusing the components.
A single number does not always say which of two solutions is better: a constraint may have to come before the objective, or two objectives may have no common unit. Chapter 3 builds those costs, and the rest of the tutorial uses the sum of this chapter.
See also¶
- Cost: cost components, cost expressions, cost models and semantics.
Next steps¶
Chapter 3 compares solutions by priority, by objective, or by a rule of your own.