31 std::set<std::size_t> forbidden_palettes;
33 explicit TileInfo(
PackableTile t) : tile{std::move(t)}, forbidden_palettes{} {}
55[[nodiscard]] std::optional<std::size_t> find_best_palette_excluding_forbidden(
57 const std::vector<PackedPalette> &palettes,
58 bool force_assignment =
false,
61 std::optional<std::size_t> best_idx;
62 double best_cost = std::numeric_limits<double>::max();
64 for (std::size_t i = 0; i < palettes.size(); ++i) {
66 if (info.forbidden_palettes.contains(i)) {
74 if (metadata !=
nullptr) {
78 if (cost < best_cost) {
87 if (!force_assignment && best_idx.has_value() && best_cost >=
static_cast<double>(info.tile.color_count())) {
96 std::size_t max_attempts;
113[[nodiscard]] std::array<OarParams, 17> build_preset_matrix()
115 std::array<OarParams, 17> matrix{};
118 constexpr std::array<std::uint64_t, 4> seeds = {42, 123, 456, 789};
121 matrix[idx++] = OarParams{ShuffleStrategy::single_ffd, 1, 42};
124 for (std::uint64_t seed : seeds) {
125 matrix[idx++] = OarParams{ShuffleStrategy::noisy_ffd, 20, seed};
129 for (std::uint64_t seed : seeds) {
130 matrix[idx++] = OarParams{ShuffleStrategy::random, 20, seed};
134 for (std::uint64_t seed : seeds) {
135 matrix[idx++] = OarParams{ShuffleStrategy::noisy_ffd, 75, seed};
139 for (std::uint64_t seed : seeds) {
140 matrix[idx++] = OarParams{ShuffleStrategy::random, 75, seed};
146[[nodiscard]] std::string format_oar_params_line(
const OarParams ¶ms)
149 "shuffle_strategy={}, max_attempts={}, seed={}.",
155void emit_success_remark(
const UserDiagnostics &diag,
const OarParams ¶ms,
bool is_preset)
157 std::vector<std::string> lines;
159 lines.emplace_back(
"Overload-and-Remove search succeeded with preset config:");
162 lines.emplace_back(
"Overload-and-Remove search succeeded:");
164 lines.emplace_back(format_oar_params_line(params));
165 diag.
remark(
"overload-and-remove-search", lines);
174 if (use_preset_matrix_) {
175 auto matrix = build_preset_matrix();
177 for (
const auto ¶ms : matrix) {
178 auto result = run_multi_start(input, params.shuffle_strategy, params.max_attempts, params.seed);
179 if (result.has_value()) {
180 if (diag_ !=
nullptr) {
181 emit_success_remark(*diag_, params,
true);
188 "Overload-and-Remove strategy failed to find a valid palette assignment after all preset configurations."};
192 OarParams params{shuffle_strategy_, max_attempts_, seed_};
193 auto result = run_multi_start(input, shuffle_strategy_, max_attempts_, seed_);
194 if (result.has_value()) {
195 if (diag_ !=
nullptr) {
196 emit_success_remark(*diag_, params,
false);
202 "Overload-and-Remove strategy failed to find a valid palette assignment with the configured parameters."};
209 auto first_result = try_pack(input, shuffle_strategy, std::nullopt);
215 std::mt19937_64 seed_generator{seed};
216 for (std::size_t attempt = 1; attempt < max_attempts; ++attempt) {
217 std::uint64_t shuffle_seed = seed_generator();
218 auto result = try_pack(input, shuffle_strategy, shuffle_seed);
219 if (result.has_value()) {
242 if (output.
pals_.empty()) {
244 return FormattableError{
"Overload-And-Remove: no palettes available in pool."};
250 std::deque<TileInfo> tile_pool;
251 for (
const auto &hint : input.hints_) {
252 tile_pool.emplace_back(hint);
254 for (
const auto &tile : input.tiles_) {
255 tile_pool.emplace_back(tile);
258 if (tile_pool.empty()) {
273 if (shuffle_seed.has_value()) {
274 std::mt19937_64 rng{shuffle_seed.value()};
275 std::ranges::shuffle(tile_pool, rng);
279 std::ranges::stable_sort(tile_pool, [](
const TileInfo &a,
const TileInfo &b) {
280 return a.tile.color_count() > b.tile.color_count();
287 std::ranges::stable_sort(tile_pool, [](
const TileInfo &a,
const TileInfo &b) {
288 return a.tile.color_count() > b.tile.color_count();
293 TileInfo first_tile_info = std::move(tile_pool.front());
294 tile_pool.pop_front();
301 bool first_assigned =
false;
302 for (std::size_t i = 0; i < output.
pals_.size(); ++i) {
303 if (output.
pals_[i].can_fit(first_tile_info.tile.color_set())) {
304 output.
pals_[i].add_tile(first_tile_info.tile);
305 output.
tile_to_pal_[first_tile_info.tile.id()] = output.
pals_[i].hardware_index();
306 first_assigned =
true;
310 if (!first_assigned) {
313 output.
pals_.back().add_tile(first_tile_info.tile);
314 output.
tile_to_pal_[first_tile_info.tile.id()] = output.
pals_.back().hardware_index();
317 return FormattableError{
"Overload-And-Remove: first tile cannot fit in any palette."};
324 std::map<PackableTile::Id, ColorSet> tile_colors_map;
325 for (
const auto &hint : input.hints_) {
326 tile_colors_map[hint.id()] = hint.color_set();
328 for (
const auto &tile : input.tiles_) {
329 tile_colors_map[tile.id()] = tile.color_set();
334 std::map<PackableTile::Id, std::set<std::size_t>> forbidden_map;
337 while (!tile_pool.empty()) {
338 TileInfo tile_info = std::move(tile_pool.front());
339 tile_pool.pop_front();
342 auto maybe_best_idx = find_best_palette_excluding_forbidden(tile_info, output.
pals_,
false, metadata);
344 if (!maybe_best_idx.has_value()) {
348 output.
pals_.back().add_tile(tile_info.tile);
362 bool assigned =
false;
363 for (std::size_t i = 0; i < output.
pals_.size(); ++i) {
364 if (!tile_info.forbidden_palettes.contains(i) && output.
pals_[i].can_fit(tile_info.tile.color_set())) {
365 output.
pals_[i].add_tile(tile_info.tile);
376 maybe_best_idx = find_best_palette_excluding_forbidden(tile_info, output.
pals_,
true, metadata);
377 if (!maybe_best_idx.has_value()) {
379 "Overload-and-Remove: cannot assign tile - all palettes forbidden - " +
385 auto best_idx = maybe_best_idx.value();
388 auto &best_palette = output.
pals_[best_idx];
389 best_palette.add_tile(tile_info.tile);
390 output.
tile_to_pal_[tile_info.tile.id()] = best_palette.hardware_index();
394 const auto &assigned_ids = best_palette.assigned_tile_ids();
395 if (assigned_ids.size() <= 1) {
401 double min_efficiency = std::numeric_limits<double>::max();
402 double max_efficiency = std::numeric_limits<double>::lowest();
407 if (std::holds_alternative<PackableTile::PrefilledPaletteId>(tid)) {
411 const auto it = tile_colors_map.find(tid);
412 if (it == tile_colors_map.end()) {
418 if (eff < min_efficiency) {
419 min_efficiency = eff;
422 if (eff > max_efficiency) {
423 max_efficiency = eff;
428 if (std::abs(min_efficiency - max_efficiency) < 1e-9) {
431 std::optional<std::size_t> best_removal_pos;
432 std::size_t best_color_count = 0;
434 for (std::size_t pos = 0; pos < assigned_ids.size(); ++pos) {
435 const auto &tid = assigned_ids[pos];
437 if (std::holds_alternative<PackableTile::PrefilledPaletteId>(tid)) {
440 const auto it = tile_colors_map.find(tid);
441 if (it == tile_colors_map.end()) {
447 if (!best_removal_pos.has_value() || cc > best_color_count ||
448 (cc == best_color_count && pos > best_removal_pos.value())) {
449 best_removal_pos = pos;
450 best_color_count = cc;
455 if (!best_removal_pos.has_value()) {
459 worst_tile_id = assigned_ids[best_removal_pos.value()];
463 best_palette.remove_tile(worst_tile_id);
467 forbidden_map[worst_tile_id].insert(best_idx);
469 if (
const auto colors_it = tile_colors_map.find(worst_tile_id); colors_it != tile_colors_map.end()) {
470 TileInfo removed_info{
PackableTile{worst_tile_id, colors_it->second}};
472 removed_info.forbidden_palettes = forbidden_map[worst_tile_id];
473 tile_pool.push_back(std::move(removed_info));
479 std::vector<TileInfo> remaining_tile_pool{};
480 for (
auto &pal : output.pals_) {
481 while (pal.color_count() > input.
pal_capacity_ && !pal.assigned_tile_ids().empty()) {
483 const auto &ids = pal.assigned_tile_ids();
484 std::optional<PackableTile::Id> removable_tid;
485 for (
auto it = ids.rbegin(); it != ids.rend(); ++it) {
486 if (!std::holds_alternative<PackableTile::PrefilledPaletteId>(*it)) {
491 if (!removable_tid.has_value()) {
495 pal.remove_tile(removable_tid.value());
498 if (
const auto it = tile_colors_map.find(removable_tid.value()); it != tile_colors_map.end()) {
499 remaining_tile_pool.emplace_back(
PackableTile{removable_tid.value(), it->second});
505 for (
auto &tile_info : remaining_tile_pool) {
506 bool assigned =
false;
507 for (std::size_t i = 0; i < output.
pals_.size(); ++i) {
508 if (output.
pals_[i].can_fit(tile_info.tile.color_set())) {
509 output.
pals_[i].add_tile(tile_info.tile);
518 output.
pals_.back().add_tile(tile_info.tile);
523 "Overload-and-Remove: cannot assign tile in final pass - " +
to_string(tile_info.tile.id())};
A result type that maintains a chainable sequence of errors for debugging and error reporting.
ChainableResult< PackingOutput > pack(const PackingInput &input) const override
Packs tiles into palettes using the Overload-And-Remove algorithm with multi-start.
Wraps a ColorSet with a tile ID for tracking during palette packing.
std::variant< HintId, PrefilledPaletteId, RegularId, AnimId, PrimaryTileId > Id
Variant type for tile identification.
Manages allocation of hardware palette indexes with stack-based checkout semantics.
std::size_t checkout()
Checks out the next available hardware palette index.
bool has_available_pal() const
Checks if there is at least one available pal that can be checked out.
Abstract class for structured error reporting and diagnostic output.
virtual void remark(const std::string &tag, const std::vector< std::string > &lines) const =0
Display a tagged remark message.
double compute_weighted_cost_in_palette_fast(const ColorSet &tile_colors, const PackedPalette &palette)
Computes the weighted cost of placing a tile in a palette using cached counts.
double compute_sharing_penalty(const PackableTile &tile, const PackedPalette &palette, const ShapeGroupMetadata &metadata, double sharing_weight=0.5)
Computes the sharing penalty for placing a tile in a palette.
ShuffleStrategy
Controls how tile orderings are generated during multi-start packing.
@ single_ffd
One FFD attempt only, no multi-start retries.
@ noisy_ffd
FFD first, then perturbed FFD orderings that preserve the large-first property.
double compute_palette_local_efficiency_fast(const ColorSet &tile_colors, const PackedPalette &palette)
Computes the palette-local efficiency of a tile using cached counts.
std::size_t color_set_count(const ColorSet &set)
Counts the number of colors in a ColorSet.
std::string to_string(const PrimaryPairingMode m)
Converts a PrimaryPairingMode to its canonical string representation.
std::vector< PackedPalette > initialize_packed_palettes(const std::set< PrefilledPalette > &prefilled_pals, PalettePool &pal_pool, std::size_t pal_capacity)
Initializes packed palettes from prefilled palettes.
Metrics for Bin Packing with Overlapping Items (Pagination problem).
The final palette assignments after a successful packing operation.
std::map< PackableTile::Id, std::size_t > tile_to_pal_
Maps tile IDs to their assigned hardware palette indices.
std::vector< PackedPalette > pals_
The packed palettes with their colors and assigned tiles.