29 std::set<std::size_t> forbidden_palettes;
31 explicit TileInfo(
PackableTile t) : tile{std::move(t)}, forbidden_palettes{} {}
51[[nodiscard]] std::optional<std::size_t> find_best_palette_excluding_forbidden(
53 const std::vector<PackedPalette> &palettes,
54 bool force_assignment =
false,
57 std::optional<std::size_t> best_idx;
58 double best_cost = std::numeric_limits<double>::max();
60 for (std::size_t i = 0; i < palettes.size(); ++i) {
62 if (info.forbidden_palettes.contains(i)) {
70 if (metadata !=
nullptr) {
74 if (cost < best_cost) {
83 if (!force_assignment && best_idx.has_value() && best_cost >=
static_cast<double>(info.tile.color_count())) {
92 std::size_t max_attempts;
107[[nodiscard]] std::array<OarParams, 17> build_preset_matrix()
109 std::array<OarParams, 17> matrix{};
112 constexpr std::array<std::uint64_t, 4> seeds = {42, 123, 456, 789};
115 matrix[idx++] = OarParams{ShuffleStrategy::single_ffd, 1, 42};
118 for (std::uint64_t seed : seeds) {
119 matrix[idx++] = OarParams{ShuffleStrategy::noisy_ffd, 20, seed};
123 for (std::uint64_t seed : seeds) {
124 matrix[idx++] = OarParams{ShuffleStrategy::random, 20, seed};
128 for (std::uint64_t seed : seeds) {
129 matrix[idx++] = OarParams{ShuffleStrategy::noisy_ffd, 75, seed};
133 for (std::uint64_t seed : seeds) {
134 matrix[idx++] = OarParams{ShuffleStrategy::random, 75, seed};
140[[nodiscard]] std::string format_oar_params_line(
const OarParams ¶ms)
143 "shuffle_strategy={}, max_attempts={}, seed={}.",
149void emit_success_remark(
const UserDiagnostics &diag,
const OarParams ¶ms,
bool is_preset)
151 std::vector<std::string> lines;
153 lines.emplace_back(
"Overload-and-Remove search succeeded with preset config:");
156 lines.emplace_back(
"Overload-and-Remove search succeeded:");
158 lines.emplace_back(format_oar_params_line(params));
159 diag.
remark(
"overload-and-remove-search", lines);
168 if (use_preset_matrix_) {
169 auto matrix = build_preset_matrix();
171 for (
const auto ¶ms : matrix) {
172 auto result = run_multi_start(input, params.shuffle_strategy, params.max_attempts, params.seed);
173 if (result.has_value()) {
174 if (diag_ !=
nullptr) {
175 emit_success_remark(*diag_, params,
true);
182 "Overload-and-Remove strategy failed to find a valid palette assignment after all preset configurations."};
186 OarParams params{shuffle_strategy_, max_attempts_, seed_};
187 auto result = run_multi_start(input, shuffle_strategy_, max_attempts_, seed_);
188 if (result.has_value()) {
189 if (diag_ !=
nullptr) {
190 emit_success_remark(*diag_, params,
false);
196 "Overload-and-Remove strategy failed to find a valid palette assignment with the configured parameters."};
203 auto first_result = try_pack(input, shuffle_strategy, std::nullopt);
209 std::mt19937_64 seed_generator{seed};
210 for (std::size_t attempt = 1; attempt < max_attempts; ++attempt) {
211 std::uint64_t shuffle_seed = seed_generator();
212 auto result = try_pack(input, shuffle_strategy, shuffle_seed);
213 if (result.has_value()) {
238 return FormattableError{
"Overload-And-Remove: no palettes available in pool."};
244 std::deque<TileInfo> tile_pool;
245 for (
const auto &hint : input.hints_) {
246 tile_pool.emplace_back(hint);
248 for (
const auto &tile : input.tiles_) {
249 tile_pool.emplace_back(tile);
252 if (tile_pool.empty()) {
265 if (shuffle_seed.has_value()) {
266 std::mt19937_64 rng{shuffle_seed.value()};
267 std::ranges::shuffle(tile_pool, rng);
271 std::ranges::stable_sort(tile_pool, [](
const TileInfo &a,
const TileInfo &b) {
272 return a.tile.color_count() > b.tile.color_count();
279 std::ranges::stable_sort(tile_pool, [](
const TileInfo &a,
const TileInfo &b) {
280 return a.tile.color_count() > b.tile.color_count();
285 TileInfo first_tile_info = std::move(tile_pool.front());
286 tile_pool.pop_front();
291 bool first_assigned =
false;
292 for (std::size_t i = 0; i < output.
palettes_.size(); ++i) {
293 if (output.
palettes_[i].can_fit(first_tile_info.tile.color_set())) {
294 output.
palettes_[i].add_tile(first_tile_info.tile);
296 first_assigned =
true;
300 if (!first_assigned) {
303 output.
palettes_.back().add_tile(first_tile_info.tile);
307 return FormattableError{
"Overload-And-Remove: first tile cannot fit in any palette."};
314 std::map<PackableTile::Id, ColorSet> tile_colors_map;
315 for (
const auto &hint : input.hints_) {
316 tile_colors_map[hint.id()] = hint.color_set();
318 for (
const auto &tile : input.tiles_) {
319 tile_colors_map[tile.id()] = tile.color_set();
324 std::map<PackableTile::Id, std::set<std::size_t>> forbidden_map;
327 while (!tile_pool.empty()) {
328 TileInfo tile_info = std::move(tile_pool.front());
329 tile_pool.pop_front();
332 auto maybe_best_idx = find_best_palette_excluding_forbidden(tile_info, output.
palettes_,
false, metadata);
334 if (!maybe_best_idx.has_value()) {
338 output.
palettes_.back().add_tile(tile_info.tile);
350 bool assigned =
false;
351 for (std::size_t i = 0; i < output.
palettes_.size(); ++i) {
352 if (!tile_info.forbidden_palettes.contains(i) &&
353 output.
palettes_[i].can_fit(tile_info.tile.color_set())) {
354 output.
palettes_[i].add_tile(tile_info.tile);
365 maybe_best_idx = find_best_palette_excluding_forbidden(tile_info, output.
palettes_,
true, metadata);
366 if (!maybe_best_idx.has_value()) {
368 "Overload-and-Remove: cannot assign tile - all palettes forbidden - " +
374 auto best_idx = maybe_best_idx.value();
377 auto &best_palette = output.
palettes_[best_idx];
378 best_palette.add_tile(tile_info.tile);
379 output.
tile_to_palette_[tile_info.tile.id()] = best_palette.hardware_index();
383 const auto &assigned_ids = best_palette.assigned_tile_ids();
384 if (assigned_ids.size() <= 1) {
390 double min_efficiency = std::numeric_limits<double>::max();
391 double max_efficiency = std::numeric_limits<double>::lowest();
396 if (std::holds_alternative<PackableTile::PrefilledPaletteId>(tid)) {
400 const auto it = tile_colors_map.find(tid);
401 if (it == tile_colors_map.end()) {
407 if (eff < min_efficiency) {
408 min_efficiency = eff;
411 if (eff > max_efficiency) {
412 max_efficiency = eff;
417 if (std::abs(min_efficiency - max_efficiency) < 1e-9) {
420 std::optional<std::size_t> best_removal_pos;
421 std::size_t best_color_count = 0;
423 for (std::size_t pos = 0; pos < assigned_ids.size(); ++pos) {
424 const auto &tid = assigned_ids[pos];
426 if (std::holds_alternative<PackableTile::PrefilledPaletteId>(tid)) {
429 const auto it = tile_colors_map.find(tid);
430 if (it == tile_colors_map.end()) {
436 if (!best_removal_pos.has_value() || cc > best_color_count ||
437 (cc == best_color_count && pos > best_removal_pos.value())) {
438 best_removal_pos = pos;
439 best_color_count = cc;
444 if (!best_removal_pos.has_value()) {
448 worst_tile_id = assigned_ids[best_removal_pos.value()];
452 best_palette.remove_tile(worst_tile_id);
456 forbidden_map[worst_tile_id].insert(best_idx);
458 if (
const auto colors_it = tile_colors_map.find(worst_tile_id); colors_it != tile_colors_map.end()) {
459 TileInfo removed_info{
PackableTile{worst_tile_id, colors_it->second}};
461 removed_info.forbidden_palettes = forbidden_map[worst_tile_id];
462 tile_pool.push_back(std::move(removed_info));
468 std::vector<TileInfo> remaining_tile_pool{};
469 for (
auto &palette : output.palettes_) {
470 while (palette.color_count() > input.
palette_capacity_ && !palette.assigned_tile_ids().empty()) {
472 const auto &ids = palette.assigned_tile_ids();
473 std::optional<PackableTile::Id> removable_tid;
474 for (
auto it = ids.rbegin(); it != ids.rend(); ++it) {
475 if (!std::holds_alternative<PackableTile::PrefilledPaletteId>(*it)) {
480 if (!removable_tid.has_value()) {
484 palette.remove_tile(removable_tid.value());
487 if (
const auto it = tile_colors_map.find(removable_tid.value()); it != tile_colors_map.end()) {
488 remaining_tile_pool.emplace_back(
PackableTile{removable_tid.value(), it->second});
494 for (
auto &tile_info : remaining_tile_pool) {
495 bool assigned =
false;
496 for (std::size_t i = 0; i < output.
palettes_.size(); ++i) {
497 if (output.
palettes_[i].can_fit(tile_info.tile.color_set())) {
498 output.
palettes_[i].add_tile(tile_info.tile);
507 output.
palettes_.back().add_tile(tile_info.tile);
512 "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.
bool has_available_palette() const
Checks if there is at least one available palette that can be checked out.
std::size_t checkout()
Checks out the next available hardware palette index.
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_palettes, PalettePool &palette_pool, std::size_t palette_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::vector< PackedPalette > palettes_
The packed palettes with their colors and assigned tiles.
std::map< PackableTile::Id, std::size_t > tile_to_palette_
Maps tile IDs to their assigned hardware palette indices.