mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-29 13:52:07 +00:00
Merge branch 'main' into feat/plugin-feature
This commit is contained in:
6
.github/workflows/build_all.yml
vendored
6
.github/workflows/build_all.yml
vendored
@@ -172,17 +172,17 @@ jobs:
|
|||||||
# binary); this covers src/engine PRs with the PR-built binary. Runs in
|
# binary); this covers src/engine PRs with the PR-built binary. Runs in
|
||||||
# parallel off build_linux's artifact so it doesn't lengthen the build leg.
|
# parallel off build_linux's artifact so it doesn't lengthen the build leg.
|
||||||
slice_check_linux:
|
slice_check_linux:
|
||||||
name: Slice check (Linux x86_64)
|
name: Slice check (Linux aarch64)
|
||||||
needs: build_linux
|
needs: build_linux
|
||||||
if: ${{ !cancelled() && success() }}
|
if: ${{ !cancelled() && success() }}
|
||||||
runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
|
runs-on: ubuntu-24.04-arm
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
- name: Download profile validator
|
- name: Download profile validator
|
||||||
uses: actions/download-artifact@v8
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: ${{ github.sha }}-profile-validator-linux-x86_64
|
name: ${{ github.sha }}-profile-validator-linux-aarch64
|
||||||
path: validator-bin
|
path: validator-bin
|
||||||
- name: Validate slice (expand custom g-code)
|
- name: Validate slice (expand custom g-code)
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
|
|||||||
8
.github/workflows/build_orca.yml
vendored
8
.github/workflows/build_orca.yml
vendored
@@ -515,13 +515,15 @@ jobs:
|
|||||||
|
|
||||||
# Ship the freshly-built validator so the parallel slice_check_linux job
|
# Ship the freshly-built validator so the parallel slice_check_linux job
|
||||||
# (build_all.yml) can slice-sweep the shipped profiles with this PR's
|
# (build_all.yml) can slice-sweep the shipped profiles with this PR's
|
||||||
# engine. Stable sha-based name mirrors the tests artifact above so the
|
# engine. Taken from the aarch64 leg so the sweep runs on arm64 (its
|
||||||
|
# GitHub-hosted runner is free, and it also exercises the arm build).
|
||||||
|
# Stable sha-based name mirrors the tests artifact above so the
|
||||||
# downstream job downloads it by exact name.
|
# downstream job downloads it by exact name.
|
||||||
- name: Upload profile validator (for slice check)
|
- name: Upload profile validator (for slice check)
|
||||||
if: runner.os == 'Linux' && inputs.arch != 'aarch64'
|
if: runner.os == 'Linux' && inputs.arch == 'aarch64'
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ github.sha }}-profile-validator-linux-x86_64
|
name: ${{ github.sha }}-profile-validator-linux-aarch64
|
||||||
overwrite: true
|
overwrite: true
|
||||||
path: ./build/src/Release/OrcaSlicer_profile_validator
|
path: ./build/src/Release/OrcaSlicer_profile_validator
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|||||||
@@ -212,14 +212,26 @@ void install_slice_context_log_sink()
|
|||||||
// -v). Unlike the static reference/placeholder checks, this expands every custom *_gcode - including
|
// -v). Unlike the static reference/placeholder checks, this expands every custom *_gcode - including
|
||||||
// change_filament_gcode at the one filament change - against the printer's fully-resolved config, so
|
// change_filament_gcode at the one filament change - against the printer's fully-resolved config, so
|
||||||
// undefined-placeholder / invalid-flow bugs surface here. Reports every offending printer and returns 1
|
// undefined-placeholder / invalid-flow bugs surface here. Reports every offending printer and returns 1
|
||||||
// if any failed, 0 otherwise. The sweep is SEQUENTIAL by necessity: Print::process() keeps
|
// if any failed, 0 otherwise. When outdir is non-empty, each printer's g-code is also written there as
|
||||||
// process-global state, so slicing printers concurrently in one process races even with per-slice
|
// "<vendor>__<printer>.gcode" for manual inspection. The sweep is SEQUENTIAL by necessity:
|
||||||
// Model+Print. Load in validation mode so the vendors are read straight from the -p profiles dir
|
// Print::process() keeps process-global state, so slicing printers concurrently in one process races
|
||||||
// (no data_dir/system tree) and -v scoping is honoured for free.
|
// even with per-slice Model+Print. Load in validation mode so the vendors are read straight from the -p
|
||||||
int slice_all_printers(const std::string &vendor)
|
// profiles dir (no data_dir/system tree) and -v scoping is honoured for free.
|
||||||
|
int slice_all_printers(const std::string &vendor, const std::string &outdir)
|
||||||
{
|
{
|
||||||
install_slice_context_log_sink();
|
install_slice_context_log_sink();
|
||||||
|
|
||||||
|
if (!outdir.empty()) {
|
||||||
|
boost::system::error_code ec;
|
||||||
|
fs::create_directories(outdir, ec);
|
||||||
|
if (ec) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Could not create output directory \"" << outdir << "\": " << ec.message();
|
||||||
|
std::cout << "Validation failed" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::cout << "Saving sliced g-code to " << outdir << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
PresetBundle bundle;
|
PresetBundle bundle;
|
||||||
bundle.set_is_validation_mode(true);
|
bundle.set_is_validation_mode(true);
|
||||||
bundle.set_vendor_to_validate(vendor); // empty == all vendors
|
bundle.set_vendor_to_validate(vendor); // empty == all vendors
|
||||||
@@ -323,6 +335,10 @@ int slice_all_printers(const std::string &vendor)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const std::string out = slice_two_color_cube_and_export(cfg, bundle.is_bbl_vendor());
|
const std::string out = slice_two_color_cube_and_export(cfg, bundle.is_bbl_vendor());
|
||||||
|
if (!outdir.empty() && !out.empty()) {
|
||||||
|
const fs::path f = fs::path(outdir) / (sanitize_filename(vendor_name) + "__" + sanitize_filename(printer) + ".gcode");
|
||||||
|
save_string_file(f, out);
|
||||||
|
}
|
||||||
if (out.empty() || out.find("G1") == std::string::npos) {
|
if (out.empty() || out.find("G1") == std::string::npos) {
|
||||||
BOOST_LOG_TRIVIAL(error) << "Printer \"" << printer << "\" produced no g-code";
|
BOOST_LOG_TRIVIAL(error) << "Printer \"" << printer << "\" produced no g-code";
|
||||||
++failures;
|
++failures;
|
||||||
@@ -364,6 +380,7 @@ int main(int argc, char* argv[])
|
|||||||
("vendor,v", po::value<std::string>()->default_value(""), "Vendor name. Optional, all profiles present in the folder will be validated if not specified")
|
("vendor,v", po::value<std::string>()->default_value(""), "Vendor name. Optional, all profiles present in the folder will be validated if not specified")
|
||||||
("generate_presets,g", po::value<bool>()->default_value(false), "Generate user presets for mock test")
|
("generate_presets,g", po::value<bool>()->default_value(false), "Generate user presets for mock test")
|
||||||
("slice,s", po::bool_switch()->default_value(false), "Slice a two-colour cube through every printer to expand all custom g-code (catches placeholder/flow errors that static checks miss). Off unless this flag is present.")
|
("slice,s", po::bool_switch()->default_value(false), "Slice a two-colour cube through every printer to expand all custom g-code (catches placeholder/flow errors that static checks miss). Off unless this flag is present.")
|
||||||
|
("outdir,o", po::value<std::string>()->default_value(""), "With -s, also save each printer's g-code to this folder (as <vendor>__<printer>.gcode) for manual inspection. Optional.")
|
||||||
("check_filament_subtypes,f", po::bool_switch()->default_value(false), "Also flag printers with duplicate (ambiguous) filament subtypes. Off unless this flag is present.")
|
("check_filament_subtypes,f", po::bool_switch()->default_value(false), "Also flag printers with duplicate (ambiguous) filament subtypes. Off unless this flag is present.")
|
||||||
("log_level,l", po::value<int>()->default_value(2), "Log level. Optional, default is 2 (warning). Higher values produce more detailed logs.");
|
("log_level,l", po::value<int>()->default_value(2), "Log level. Optional, default is 2 (warning). Higher values produce more detailed logs.");
|
||||||
// clang-format on
|
// clang-format on
|
||||||
@@ -389,6 +406,7 @@ int main(int argc, char* argv[])
|
|||||||
int log_level = vm["log_level"].as<int>();
|
int log_level = vm["log_level"].as<int>();
|
||||||
bool generate_user_preset = vm["generate_presets"].as<bool>();
|
bool generate_user_preset = vm["generate_presets"].as<bool>();
|
||||||
bool slice_mode = vm["slice"].as<bool>();
|
bool slice_mode = vm["slice"].as<bool>();
|
||||||
|
std::string slice_outdir = vm["outdir"].as<std::string>();
|
||||||
bool check_filament_subtypes = vm["check_filament_subtypes"].as<bool>();
|
bool check_filament_subtypes = vm["check_filament_subtypes"].as<bool>();
|
||||||
|
|
||||||
// check if path is valid, and return error if not
|
// check if path is valid, and return error if not
|
||||||
@@ -419,7 +437,7 @@ int main(int argc, char* argv[])
|
|||||||
// Slice mode expands every printer's custom g-code by actually slicing (see slice_all_printers).
|
// Slice mode expands every printer's custom g-code by actually slicing (see slice_all_printers).
|
||||||
// A distinct opt-in mode so the default static checks stay fast for every profile PR.
|
// A distinct opt-in mode so the default static checks stay fast for every profile PR.
|
||||||
if (slice_mode)
|
if (slice_mode)
|
||||||
return slice_all_printers(vendor);
|
return slice_all_printers(vendor, slice_outdir);
|
||||||
|
|
||||||
auto preset_bundle = new PresetBundle();
|
auto preset_bundle = new PresetBundle();
|
||||||
// preset_bundle->setup_directories();
|
// preset_bundle->setup_directories();
|
||||||
|
|||||||
@@ -352,8 +352,7 @@ namespace Slic3r
|
|||||||
int k,
|
int k,
|
||||||
const std::vector<unsigned int>& used_filaments,
|
const std::vector<unsigned int>& used_filaments,
|
||||||
const std::unordered_map<int, std::vector<int>>& unplaceable_limits,
|
const std::unordered_map<int, std::vector<int>>& unplaceable_limits,
|
||||||
int* cost,
|
int* cost)
|
||||||
int timeout_ms)
|
|
||||||
{
|
{
|
||||||
auto distance_evaluator = std::make_shared<FlushDistanceEvaluator>(ctx.model_info.flush_matrix, used_filaments, ctx.model_info.layer_filaments);
|
auto distance_evaluator = std::make_shared<FlushDistanceEvaluator>(ctx.model_info.flush_matrix, used_filaments, ctx.model_info.layer_filaments);
|
||||||
KMediods PAM(k, (int)used_filaments.size(), distance_evaluator, ctx.machine_info.master_extruder_id);
|
KMediods PAM(k, (int)used_filaments.size(), distance_evaluator, ctx.machine_info.master_extruder_id);
|
||||||
@@ -369,7 +368,7 @@ namespace Slic3r
|
|||||||
}
|
}
|
||||||
PAM.set_cluster_group_size(cluster_size_limit);
|
PAM.set_cluster_group_size(cluster_size_limit);
|
||||||
|
|
||||||
PAM.do_clustering(ctx, timeout_ms, 30);
|
PAM.do_clustering(ctx, m_clustering_budget);
|
||||||
|
|
||||||
m_memoryed_heap = PAM.get_memoryed_groups();
|
m_memoryed_heap = PAM.get_memoryed_groups();
|
||||||
|
|
||||||
@@ -793,7 +792,7 @@ namespace Slic3r
|
|||||||
2.1 In each cluster, make the point that minimizes the sum of distances within the cluster the medoid
|
2.1 In each cluster, make the point that minimizes the sum of distances within the cluster the medoid
|
||||||
2.2 Reassign each point to the cluster defined by the closest medoid determined in the previous step
|
2.2 Reassign each point to the cluster defined by the closest medoid determined in the previous step
|
||||||
*/
|
*/
|
||||||
void KMediods::do_clustering(const FilamentGroupContext &context, int timeout_ms, int retry)
|
void KMediods::do_clustering(const FilamentGroupContext& context, const ClusteringBudget& budget)
|
||||||
{
|
{
|
||||||
FlushTimeMachine T;
|
FlushTimeMachine T;
|
||||||
T.time_machine_start();
|
T.time_machine_start();
|
||||||
@@ -817,7 +816,11 @@ namespace Slic3r
|
|||||||
double best_cluster_cost = std::numeric_limits<double>::max();
|
double best_cluster_cost = std::numeric_limits<double>::max();
|
||||||
int retry_count = 0;
|
int retry_count = 0;
|
||||||
|
|
||||||
while (retry_count < retry && T.time_machine_end() < timeout_ms) {
|
// Run at least one restart; otherwise every filament would stay in the default group.
|
||||||
|
const int retry = std::max(1, budget.max_restarts);
|
||||||
|
auto within_budget = [&]() { return budget.timeout_ms <= 0 || T.time_machine_end() < budget.timeout_ms; };
|
||||||
|
|
||||||
|
while (retry_count < retry && within_budget()) {
|
||||||
std::vector<int> curr_cluster_centers = init_cluster_center(m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size, retry_count);
|
std::vector<int> curr_cluster_centers = init_cluster_center(m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size, retry_count);
|
||||||
std::vector<int> curr_cluster_labels = assign_cluster_label(curr_cluster_centers, m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size);
|
std::vector<int> curr_cluster_labels = assign_cluster_label(curr_cluster_centers, m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size);
|
||||||
double curr_cluster_cost = evaluate_labels(curr_cluster_labels);
|
double curr_cluster_cost = evaluate_labels(curr_cluster_labels);
|
||||||
@@ -826,7 +829,7 @@ namespace Slic3r
|
|||||||
update_memoryed_groups(g, memory_threshold, memoryed_groups);
|
update_memoryed_groups(g, memory_threshold, memoryed_groups);
|
||||||
|
|
||||||
bool mediods_changed = true;
|
bool mediods_changed = true;
|
||||||
while (mediods_changed && T.time_machine_end() < timeout_ms) {
|
while (mediods_changed && within_budget()) {
|
||||||
mediods_changed = false;
|
mediods_changed = false;
|
||||||
double best_swap_cost = curr_cluster_cost;
|
double best_swap_cost = curr_cluster_cost;
|
||||||
int best_swap_cluster = -1;
|
int best_swap_cluster = -1;
|
||||||
@@ -889,7 +892,7 @@ namespace Slic3r
|
|||||||
if (estimated < ENUM_THRESHOLD)
|
if (estimated < ENUM_THRESHOLD)
|
||||||
result = calc_group_by_enum(k, used_filaments, unplaceable_limits, cost);
|
result = calc_group_by_enum(k, used_filaments, unplaceable_limits, cost);
|
||||||
else
|
else
|
||||||
result = calc_group_by_kmedoids(k, used_filaments, unplaceable_limits, cost, 3000);
|
result = calc_group_by_kmedoids(k, used_filaments, unplaceable_limits, cost);
|
||||||
|
|
||||||
change_memoryed_heaps_to_arrays(m_memoryed_heap, ctx.group_info.total_filament_num, used_filaments, m_memoryed_groups);
|
change_memoryed_heaps_to_arrays(m_memoryed_heap, ctx.group_info.total_filament_num, used_filaments, m_memoryed_groups);
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,16 @@ namespace Slic3r
|
|||||||
FilamentGroupContext::SpeedInfo m_speed_info;
|
FilamentGroupContext::SpeedInfo m_speed_info;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Search budget for the k-medoids clustering, an anytime search. Each restart is seeded from its
|
||||||
|
// own index, so what it returns depends on how many restarts complete before the clock expires,
|
||||||
|
// and therefore on the speed of the machine. A timeout_ms <= 0 removes the clock and bounds the
|
||||||
|
// search by max_restarts alone.
|
||||||
|
struct ClusteringBudget
|
||||||
|
{
|
||||||
|
int timeout_ms = 3000;
|
||||||
|
int max_restarts = 30;
|
||||||
|
};
|
||||||
|
|
||||||
class FilamentGroup
|
class FilamentGroup
|
||||||
{
|
{
|
||||||
using MemoryedGroup = FilamentGroupUtils::MemoryedGroup;
|
using MemoryedGroup = FilamentGroupUtils::MemoryedGroup;
|
||||||
@@ -149,6 +159,8 @@ namespace Slic3r
|
|||||||
public:
|
public:
|
||||||
explicit FilamentGroup(const FilamentGroupContext& ctx_) :ctx(ctx_) {}
|
explicit FilamentGroup(const FilamentGroupContext& ctx_) :ctx(ctx_) {}
|
||||||
public:
|
public:
|
||||||
|
void set_clustering_budget(const ClusteringBudget& budget) { m_clustering_budget = budget; }
|
||||||
|
|
||||||
std::vector<int> calc_filament_group(int * cost = nullptr);
|
std::vector<int> calc_filament_group(int * cost = nullptr);
|
||||||
std::vector<std::vector<int>> get_memoryed_groups()const { return m_memoryed_groups; }
|
std::vector<std::vector<int>> get_memoryed_groups()const { return m_memoryed_groups; }
|
||||||
|
|
||||||
@@ -162,7 +174,7 @@ namespace Slic3r
|
|||||||
std::vector<int> calc_group_by_enum(int k, const std::vector<unsigned int>& used_filaments,
|
std::vector<int> calc_group_by_enum(int k, const std::vector<unsigned int>& used_filaments,
|
||||||
const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr);
|
const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr);
|
||||||
std::vector<int> calc_group_by_kmedoids(int k, const std::vector<unsigned int>& used_filaments,
|
std::vector<int> calc_group_by_kmedoids(int k, const std::vector<unsigned int>& used_filaments,
|
||||||
const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr, int timeout_ms = 500);
|
const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr);
|
||||||
|
|
||||||
std::map<int, int> rebuild_unprintables(const std::vector<unsigned int>& used_filaments, const std::map<int,int>& extruder_unprintables);
|
std::map<int, int> rebuild_unprintables(const std::vector<unsigned int>& used_filaments, const std::map<int,int>& extruder_unprintables);
|
||||||
std::unordered_map<int, std::vector<int>> rebuild_nozzle_unprintables(const std::vector<unsigned int>& used_filaments, const std::unordered_map<int, std::vector<int>>& extruder_unprintables, const std::vector<int>& filament_volume_map);
|
std::unordered_map<int, std::vector<int>> rebuild_nozzle_unprintables(const std::vector<unsigned int>& used_filaments, const std::unordered_map<int, std::vector<int>>& extruder_unprintables, const std::vector<int>& filament_volume_map);
|
||||||
@@ -175,6 +187,7 @@ namespace Slic3r
|
|||||||
FilamentGroupContext ctx;
|
FilamentGroupContext ctx;
|
||||||
MemoryedGroupHeap m_memoryed_heap;
|
MemoryedGroupHeap m_memoryed_heap;
|
||||||
std::vector<std::vector<int>> m_memoryed_groups;
|
std::vector<std::vector<int>> m_memoryed_groups;
|
||||||
|
ClusteringBudget m_clustering_budget;
|
||||||
public:
|
public:
|
||||||
std::optional<std::function<bool(int, std::vector<int>&)>> get_custom_seq;
|
std::optional<std::function<bool(int, std::vector<int>&)>> get_custom_seq;
|
||||||
};
|
};
|
||||||
@@ -220,7 +233,7 @@ namespace Slic3r
|
|||||||
void set_memory_threshold(double threshold) { memory_threshold = threshold; }
|
void set_memory_threshold(double threshold) { memory_threshold = threshold; }
|
||||||
MemoryedGroupHeap get_memoryed_groups()const { return memoryed_groups; }
|
MemoryedGroupHeap get_memoryed_groups()const { return memoryed_groups; }
|
||||||
|
|
||||||
void do_clustering(const FilamentGroupContext& context, int timeout_ms = 100, int retry = 10);
|
void do_clustering(const FilamentGroupContext& context, const ClusteringBudget& budget);
|
||||||
std::vector<int> get_cluster_labels()const { return m_cluster_labels; }
|
std::vector<int> get_cluster_labels()const { return m_cluster_labels; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -210,13 +210,15 @@ inline FullEvalResult full_evaluate_map(const FilamentGroupContext& ctx,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline TestResult run_and_evaluate(const FilamentGroupContext& ctx) {
|
inline TestResult run_and_evaluate(const FilamentGroupContext& ctx,
|
||||||
|
const ClusteringBudget& budget = {}) {
|
||||||
TestResult result;
|
TestResult result;
|
||||||
|
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
int algo_cost = 0;
|
int algo_cost = 0;
|
||||||
|
|
||||||
FilamentGroup fg(ctx);
|
FilamentGroup fg(ctx);
|
||||||
|
fg.set_clustering_budget(budget);
|
||||||
result.filament_map = fg.calc_filament_group(&algo_cost);
|
result.filament_map = fg.calc_filament_group(&algo_cost);
|
||||||
|
|
||||||
auto end = std::chrono::high_resolution_clock::now();
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
|||||||
@@ -211,19 +211,23 @@ static std::vector<PropertySpec>& get_property_specs() {
|
|||||||
return specs;
|
return specs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orca: a small number of config_c "stress" goldens run the nozzle-centric kmedoids clustering path,
|
// Under the default wall clock the result depends on how fast the machine is (see ClusteringBudget),
|
||||||
// which is bounded by a 3000 ms wall-clock budget (FilamentGroup.cpp calc_group_by_kmedoids). On
|
// so the goldens are graded under a fixed budget instead. Two restarts is the fewest that reaches
|
||||||
// slower hardware the clustering explores fewer restarts and lands on a deterministically-worse-but-
|
// parity with the reference on every golden, stress_79 being the last to get there. Four leaves
|
||||||
// valid grouping than the stored golden. We regression-lock those against Orca's own deterministic
|
// margin, since the search follows a different path on each standard library (see below).
|
||||||
// score (bit-stable across runs on this machine — verified twice) so the gate stays green while the
|
static constexpr ClusteringBudget FIXED_SEARCH_BUDGET{
|
||||||
// divergence is documented; every other golden is a true parity gate at 3% tolerance.
|
/*timeout_ms*/ 0, // no wall clock
|
||||||
static std::optional<double> orca_locked_base_score(const std::string& stem) {
|
/*max_restarts*/ 4};
|
||||||
if (stem == "stress_66") return 125103.0; // config_c 15-filament kmedoids case; golden 117843
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============ Layer 1: Golden Regression (all configs) ============
|
// ============ Layer 1: Golden Regression (all configs) ============
|
||||||
|
|
||||||
|
// Graded against the BambuStudio golden the harness was ported from, one-directional at 3%.
|
||||||
|
//
|
||||||
|
// The tolerance is a parity allowance, and it also covers a small spread across standard libraries.
|
||||||
|
// The k-medoids search seeds each restart with std::shuffle, whose algorithm the C++ standard leaves
|
||||||
|
// unspecified, so libstdc++, libc++ and the MSVC STL permute the same seed differently, start from
|
||||||
|
// different medoids, and settle on slightly different groupings, about 3e-4 apart on either side of
|
||||||
|
// the reference, and only on the goldens heavy enough to reach the k-medoids search.
|
||||||
TEST_CASE("FilamentGroup golden regression", "[filament_group][golden]") {
|
TEST_CASE("FilamentGroup golden regression", "[filament_group][golden]") {
|
||||||
auto files = get_golden_files();
|
auto files = get_golden_files();
|
||||||
if (files.empty()) {
|
if (files.empty()) {
|
||||||
@@ -238,29 +242,49 @@ TEST_CASE("FilamentGroup golden regression", "[filament_group][golden]") {
|
|||||||
auto tc = load_test_case(file_path);
|
auto tc = load_test_case(file_path);
|
||||||
REQUIRE(tc.base_result.has_value());
|
REQUIRE(tc.base_result.has_value());
|
||||||
|
|
||||||
auto result = run_and_evaluate(tc.context);
|
auto result = run_and_evaluate(tc.context, FIXED_SEARCH_BUDGET);
|
||||||
auto eval = full_evaluate_map(tc.context, result.filament_map);
|
auto eval = full_evaluate_map(tc.context, result.filament_map);
|
||||||
|
|
||||||
auto& base = *tc.base_result;
|
auto& base = *tc.base_result;
|
||||||
|
|
||||||
// Reference score: the stored golden by default; Orca's deterministic score for the
|
|
||||||
// documented heuristic-divergent config_c stress golden (see orca_locked_base_score).
|
|
||||||
std::string stem = fs::path(file_path).stem().string();
|
|
||||||
double base_score = base.full_score;
|
|
||||||
if (auto locked = orca_locked_base_score(stem))
|
|
||||||
base_score = *locked;
|
|
||||||
|
|
||||||
INFO("Case: " << tc.metadata.id);
|
INFO("Case: " << tc.metadata.id);
|
||||||
INFO("Reference score: " << base_score << " (BBS golden " << base.full_score << ")");
|
INFO("Golden score: " << base.full_score);
|
||||||
INFO("Actual score: " << eval.full_score);
|
INFO("Actual score: " << eval.full_score);
|
||||||
INFO("Flush cost: " << eval.flush_cost << " (BBS golden " << base.flush_cost << ")");
|
INFO("Flush cost: " << eval.flush_cost << " (golden " << base.flush_cost << ")");
|
||||||
INFO("Elapsed: " << result.elapsed_ms << " ms");
|
INFO("Elapsed: " << result.elapsed_ms << " ms");
|
||||||
|
|
||||||
int tolerance = std::max(50, (int)(base_score * 0.03));
|
int tolerance = std::max(50, (int)(base.full_score * 0.03));
|
||||||
|
|
||||||
REQUIRE(result.constraints_ok);
|
REQUIRE(result.constraints_ok);
|
||||||
REQUIRE(eval.full_score <= base_score + tolerance);
|
REQUIRE(eval.full_score <= base.full_score + tolerance);
|
||||||
// RelWithDebInfo runaway guard; the Release-calibrated 20 s limit is raised for the slower build.
|
|
||||||
|
// A slower search still scores the same above, since it searches just as far, but in slicing
|
||||||
|
// it would mean fewer restarts fit in the wall clock and so worse groupings. Loose on
|
||||||
|
// purpose, so it never becomes a proxy for how loaded the runner is.
|
||||||
|
const double throughput_ceiling_ms = 10.0 * ClusteringBudget{}.timeout_ms;
|
||||||
|
REQUIRE(result.elapsed_ms < throughput_ceiling_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Covers the path real slicing takes, under the default wall clock. The score there depends on the
|
||||||
|
// runner rather than on the code (see FIXED_SEARCH_BUDGET), so the only things worth asserting are
|
||||||
|
// that the grouping comes back valid and that the search terminates.
|
||||||
|
TEST_CASE("FilamentGroup returns a valid grouping under the default budget", "[filament_group][budget]") {
|
||||||
|
auto files = get_golden_files();
|
||||||
|
REQUIRE(!files.empty());
|
||||||
|
|
||||||
|
auto file_path = GENERATE_REF(from_range(files));
|
||||||
|
|
||||||
|
DYNAMIC_SECTION("Golden: " << fs::path(file_path).stem().string()) {
|
||||||
|
auto tc = load_test_case(file_path);
|
||||||
|
|
||||||
|
auto result = run_and_evaluate(tc.context); // the default budget, as real slicing runs it
|
||||||
|
|
||||||
|
INFO("Case: " << tc.metadata.id);
|
||||||
|
INFO("Elapsed: " << result.elapsed_ms << " ms");
|
||||||
|
|
||||||
|
REQUIRE(result.constraints_ok);
|
||||||
|
// A hang guard. The clock is only checked between swaps, so a sweep can overshoot.
|
||||||
REQUIRE(result.elapsed_ms < 40000.0);
|
REQUIRE(result.elapsed_ms < 40000.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user