diff --git a/benchmarks/linear_programming/cuopt/run_mip.cpp b/benchmarks/linear_programming/cuopt/run_mip.cpp index e01e533a65..a8e9f7e9c5 100644 --- a/benchmarks/linear_programming/cuopt/run_mip.cpp +++ b/benchmarks/linear_programming/cuopt/run_mip.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -137,6 +138,58 @@ std::vector> read_solution_from_dir(const std::string file_p return initial_solutions; } +struct incumbent_record_t { + double objective; + double work_timestamp; + double wall_time; + cuopt::internals::mip_solution_origin_t origin; +}; + +class incumbent_tracker_t : public cuopt::internals::get_solution_callback_ext_t { + public: + incumbent_tracker_t(std::chrono::high_resolution_clock::time_point start_time) + : start_time_(start_time) + { + } + + void get_solution(void* data, + void* cost, + void* solution_bound, + const cuopt::internals::mip_solution_callback_info_t* info, + void* user_data) override + { + double obj = *static_cast(cost); + double wt = (info != nullptr) ? info->work_timestamp : -1.0; + auto origin = + (info != nullptr) ? info->origin : cuopt::internals::mip_solution_origin_t::UNKNOWN; + auto now = std::chrono::high_resolution_clock::now(); + double wall_s = std::chrono::duration(now - start_time_).count(); + records_.push_back({obj, wt, wall_s, origin}); + } + + void write_csv(const std::string& path) const + { + std::ofstream f(path); + if (!f.is_open()) { + fprintf(stderr, "Failed to open incumbent CSV: %s\n", path.c_str()); + return; + } + f << "index,objective,work_timestamp,wall_time_s,origin\n"; + for (size_t i = 0; i < records_.size(); ++i) { + auto& r = records_[i]; + f << i << "," << std::setprecision(15) << r.objective << "," << r.work_timestamp << "," + << std::setprecision(6) << r.wall_time << "," + << cuopt::internals::mip_solution_origin_to_string(r.origin) << "\n"; + } + } + + size_t size() const { return records_.size(); } + + private: + std::chrono::high_resolution_clock::time_point start_time_; + std::vector records_; +}; + int run_single_file(std::string file_path, int device, int batch_id, @@ -203,28 +256,47 @@ int run_single_file(std::string file_path, } } } - settings.time_limit = time_limit; - settings.work_limit = work_limit; - settings.heuristics_only = heuristics_only; - settings.num_cpu_threads = num_cpu_threads; - settings.log_to_console = log_to_console; - settings.determinism_mode = deterministic ? CUOPT_MODE_DETERMINISTIC : CUOPT_MODE_OPPORTUNISTIC; + settings.time_limit = time_limit; + settings.work_limit = work_limit; + settings.heuristics_only = heuristics_only; + settings.num_cpu_threads = num_cpu_threads; + settings.log_to_console = log_to_console; + if (deterministic) { + settings.determinism_mode = + heuristics_only ? CUOPT_MODE_DETERMINISTIC_GPU_HEURISTICS : CUOPT_MODE_DETERMINISTIC; + } else { + settings.determinism_mode = CUOPT_MODE_OPPORTUNISTIC; + } + CUOPT_LOG_INFO( + "1run_mip settings: heuristics_only=%d deterministic=%d determinism_mode=%d " + "time_limit=%.6f work_limit=%.6f", + (int)heuristics_only, + (int)deterministic, + settings.determinism_mode, + settings.time_limit, + settings.work_limit); settings.tolerances.relative_tolerance = 1e-12; settings.tolerances.absolute_tolerance = 1e-6; settings.presolver = cuopt::linear_programming::presolver_t::Default; settings.reliability_branching = reliability_branching; settings.clique_cuts = -1; settings.seed = 42; + settings.bnb_work_unit_scale = 1; + settings.gpu_heur_work_unit_scale = 0.4; + settings.mip_scaling = false; + settings.gpu_heur_wait_for_exploration = false; cuopt::linear_programming::benchmark_info_t benchmark_info; settings.benchmark_info_ptr = &benchmark_info; auto start_run_solver = std::chrono::high_resolution_clock::now(); + incumbent_tracker_t incumbent_tracker(start_run_solver); + settings.set_mip_callback(&incumbent_tracker); auto solution = cuopt::linear_programming::solve_mip(&handle_, mps_data_model, settings); CUOPT_LOG_INFO( "first obj: %f last improvement of best feasible: %f last improvement after recombination: %f", benchmark_info.objective_of_initial_population, benchmark_info.last_improvement_of_best_feasible, benchmark_info.last_improvement_after_recombination); - // solution.write_to_sol_file(base_filename + ".sol", handle_.get_stream()); + // 1solution.write_to_sol_file(base_filename + ".sol", handle_.get_stream()); std::chrono::milliseconds duration; auto end = std::chrono::high_resolution_clock::now(); duration = std::chrono::duration_cast(end - start_run_solver); @@ -253,7 +325,13 @@ int run_single_file(std::string file_path, << benchmark_info.last_improvement_after_recombination << "," << mip_gap << "," << is_optimal << "\n"; write_to_output_file(out_dir, base_filename, device, n_gpus, batch_id, ss.str()); - CUOPT_LOG_INFO("Results written to the file %s", base_filename.c_str()); + if (!out_dir.empty()) { + std::string mps_stem = base_filename.substr(0, base_filename.find(".mps")); + std::string csv_path = out_dir + "/" + mps_stem + "_incumbents.csv"; + incumbent_tracker.write_csv(csv_path); + CUOPT_LOG_INFO( + "Incumbent trace (%zu entries) written to %s", incumbent_tracker.size(), csv_path.c_str()); + } return sol_found; } @@ -527,7 +605,7 @@ int main(int argc, char* argv[]) sleep(1); } int remaining = paths.size() - tests_ran; - // wait for all processes to finish + // Wait for all processes to finish for (int i = 0; i < remaining; ++i) { return_gpu_to_the_queue(pid_gpu_map, pid_file_map, gpu_queue); } diff --git a/ci/compute-sanitizer-suppressions.xml b/ci/compute-sanitizer-suppressions.xml new file mode 100644 index 0000000000..624b3aa0bd --- /dev/null +++ b/ci/compute-sanitizer-suppressions.xml @@ -0,0 +1,249 @@ + + + + Initcheck + + Uninitialized __global__ memory read of size 4 bytes + 4 + + + .* + + + + .*libcuda.so.* + + + cusparseCsr2cscEx2 + .*libcusparse.so.* + + + + + Initcheck + + Uninitialized __global__ memory read of size 4 bytes + 4 + + + ThreadLoad + + + + .*libcuda.so.* + + + libcudart.* + + + cudaLaunchKernel + + + .*cub::.*::Device(Segmented)?(Reduce|Scan)(SingleTile)?Kernel.* + + + + + Initcheck + + Uninitialized __global__ memory read of size 2 bytes + 2 + + + ThreadLoad + + + + .*libcuda.so.* + + + libcudart.* + + + cudaLaunchKernel + + + .*cub::.*::Device(Segmented)?(Reduce|Scan)(SingleTile)?Kernel.* + + + + + Initcheck + + Uninitialized __global__ memory read of size 8 bytes + 8 + + + DeviceSegmentedReduceKernel + + + + Initcheck + + Uninitialized __global__ memory read of size 4 bytes + 4 + + + ThreadLoad + + + + .*libcuda.so.* + + + libcudart.* + + + libcudart.* + + + .*libcuopt.* + + + .*Device(Reduce|Scan)Kernel.* + + + + + + + InitcheckApiError + Error + + Host API uninitialized memory access + 16 + + + + cuMemcpyDtoHAsync.* + .*libcuda.so.* + + + + + + InitcheckApiError + Error + + Host API uninitialized memory access + + + + cuMemcpyAsync + .*libcuda.so.* + + + .*libcudart.so.* + + + .*libcudart.so.* + + + .*libcudart.so.* + + + .*librmm.so.* + + + rmm::device_buffer::device_buffer + .*librmm.so.* + + + + + + Initcheck + + Uninitialized __global__ memory read + + + transform_kernel + + + + cuLaunchKernel_ptsz + .*libcuda.so.* + + + .*libcudart.so.* + + + cudaLaunchKernel_ptsz + + + + + InitcheckApiError + Error + + Host API uninitialized memory access + + + + cuMemcpyAsync + .*libcuda.so.* + + + .*libcudart.so.* + + + .*libcudart.so.* + + + .*libcudart.so.* + + + .*librmm.so.* + + + .*librmm.so.* + + + rmm::device_uvector.*::device_uvector + .*libcuopt.so.* + + + + + + InitcheckApiError + Error + + Host API uninitialized memory access + + + + cuMemcpyDtoDAsync.* + .*libcuda.so.* + + + + + InitcheckApiError + Error + + Host API uninitialized memory access + + + + cuMemcpyAsync + .*libcuda.so.* + + + .*libcudart.so.* + + + .*libcudart.so.* + + + cudaMemcpyAsync + + + rmm::device_buffer::resize + .*librmm.so.* + + + + diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 8225d93655..cb6fd779d9 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -386,6 +386,17 @@ target_link_libraries(cuopt ${CUOPT_PRIVATE_CUDA_LIBS} ) +# find_path(PAPI_INCLUDE_DIR papi.h) +# find_library(PAPI_LIBRARY papi) + +# if (PAPI_INCLUDE_DIR AND PAPI_LIBRARY) +# message(STATUS "Found PAPI in ${PAPI_INCLUDE_DIR}") +# target_include_directories(cuopt PRIVATE ${PAPI_INCLUDE_DIR}) +# target_link_libraries(cuopt PRIVATE ${PAPI_LIBRARY}) +# else() +# message(FATAL_ERROR "Could not find PAPI") +# endif() + # ################################################################################################## # - generate tests -------------------------------------------------------------------------------- diff --git a/cpp/include/cuopt/linear_programming/constants.h b/cpp/include/cuopt/linear_programming/constants.h index 86becfe06d..8467eeefc3 100644 --- a/cpp/include/cuopt/linear_programming/constants.h +++ b/cpp/include/cuopt/linear_programming/constants.h @@ -77,9 +77,35 @@ #define CUOPT_RANDOM_SEED "random_seed" #define CUOPT_PDLP_PRECISION "pdlp_precision" -/* @brief MIP determinism mode constants */ -#define CUOPT_MODE_OPPORTUNISTIC 0 -#define CUOPT_MODE_DETERMINISTIC 1 +/* @brief MIP determinism mode flags (bitset) */ +#define CUOPT_DETERMINISM_NONE 0x0 +#define CUOPT_DETERMINISM_BB \ + 0x1 // matches the previous value of '1' which was for B&B-only determinism in the previous + // rleease +#define CUOPT_DETERMINISM_GPU_HEURISTICS 0x2 +#define CUOPT_DETERMINISM_FULL (CUOPT_DETERMINISM_BB | CUOPT_DETERMINISM_GPU_HEURISTICS) + +/* Backward compatibility aliases */ +#define CUOPT_MODE_OPPORTUNISTIC CUOPT_DETERMINISM_NONE +#define CUOPT_MODE_DETERMINISTIC CUOPT_DETERMINISM_FULL +#define CUOPT_MODE_DETERMINISTIC_BB CUOPT_DETERMINISM_BB +#define CUOPT_MODE_DETERMINISTIC_GPU_HEURISTICS CUOPT_DETERMINISM_GPU_HEURISTICS + +/* @brief MIP solution origin constants */ +#define CUOPT_MIP_SOLUTION_ORIGIN_UNKNOWN 0 +#define CUOPT_MIP_SOLUTION_ORIGIN_BRANCH_AND_BOUND 1 +#define CUOPT_MIP_SOLUTION_ORIGIN_BRANCH_AND_BOUND_DIVING 2 +#define CUOPT_MIP_SOLUTION_ORIGIN_FEASIBILITY_JUMP 3 +#define CUOPT_MIP_SOLUTION_ORIGIN_CPU_FEASIBILITY_JUMP 4 +#define CUOPT_MIP_SOLUTION_ORIGIN_LOCAL_SEARCH 5 +#define CUOPT_MIP_SOLUTION_ORIGIN_QUICK_FEASIBLE 6 +#define CUOPT_MIP_SOLUTION_ORIGIN_LP_ROUNDING 7 +#define CUOPT_MIP_SOLUTION_ORIGIN_RECOMBINATION 8 +#define CUOPT_MIP_SOLUTION_ORIGIN_SUB_MIP 9 +#define CUOPT_MIP_SOLUTION_ORIGIN_USER_INITIAL 10 +#define CUOPT_MIP_SOLUTION_ORIGIN_USER_INJECTED 11 +#define CUOPT_MIP_SOLUTION_ORIGIN_RINS 12 +#define CUOPT_MIP_SOLUTION_ORIGIN_PRESOLVE 13 /* @brief LP/MIP termination status constants */ #define CUOPT_TERIMINATION_STATUS_NO_TERMINATION 0 diff --git a/cpp/include/cuopt/linear_programming/cuopt_c.h b/cpp/include/cuopt/linear_programming/cuopt_c.h index 4c4d44c764..d49cd703af 100644 --- a/cpp/include/cuopt/linear_programming/cuopt_c.h +++ b/cpp/include/cuopt/linear_programming/cuopt_c.h @@ -71,6 +71,12 @@ typedef int32_t cuopt_int_t; typedef int64_t cuopt_int_t; #endif +typedef struct { + uint64_t struct_size; + uint32_t origin; + double work_timestamp; +} cuOptMIPSolutionCallbackInfo; + /** * @brief Get the size of the float type. * @@ -713,6 +719,25 @@ typedef void (*cuOptMIPGetSolutionCallback)(const cuopt_float_t* solution, const cuopt_float_t* solution_bound, void* user_data); +/** + * @brief Type of callback for receiving incumbent MIP solutions with extensible metadata. + * + * @param[in] solution - Pointer to incumbent solution values. + * @param[in] objective_value - Pointer to incumbent objective value. + * @param[in] solution_bound - Pointer to current solution (dual/user) bound. + * @param[in] callback_info - Pointer to callback metadata. `struct_size` is always set and can be + * used to detect future extensions safely. + * @param[in] user_data - Pointer to user data. + * @note All pointer arguments refer to host memory and are only valid during the callback + * invocation. Do not pass device/GPU pointers. Copy any data you need to keep after the callback + * returns. + */ +typedef void (*cuOptMIPGetSolutionCallbackExt)(const cuopt_float_t* solution, + const cuopt_float_t* objective_value, + const cuopt_float_t* solution_bound, + const cuOptMIPSolutionCallbackInfo* callback_info, + void* user_data); + /** * @brief Type of callback for injecting MIP solutions with user context. * @@ -748,6 +773,19 @@ cuopt_int_t cuOptSetMIPGetSolutionCallback(cuOptSolverSettings settings, cuOptMIPGetSolutionCallback callback, void* user_data); +/** + * @brief Register an extended callback to receive incumbent MIP solutions with origin metadata. + * + * @param[in] settings - The solver settings object. + * @param[in] callback - Callback function to receive incumbent solutions and callback metadata. + * @param[in] user_data - User-defined pointer passed through to the callback. + * + * @return A status code indicating success or failure. + */ +cuopt_int_t cuOptSetMIPGetSolutionCallbackExt(cuOptSolverSettings settings, + cuOptMIPGetSolutionCallbackExt callback, + void* user_data); + /** * @brief Register a callback to inject MIP solutions. * diff --git a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp index 2c92d26231..8545732e43 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp @@ -101,6 +101,15 @@ class mip_solver_settings_t { i_t mip_batch_pdlp_strong_branching = 0; i_t num_gpus = 1; bool log_to_console = true; + // Scales deterministic CPUFJ producer work units before they are exposed to B&B replay/sync. + f_t cpufj_work_unit_scale = 1.0; + // Scales deterministic GPU heuristic producer work units/timestamps exposed to B&B replay/sync. + f_t gpu_heur_work_unit_scale = 1.0; + // Scales deterministic B&B work units (LP iterations) exposed to the shared deterministic + // timeline. + f_t bnb_work_unit_scale = 1.0; + // When true, GPU heuristics wait for B&B to finish root solve before starting. + bool gpu_heur_wait_for_exploration = true; std::string log_file; std::string sol_file; @@ -111,15 +120,15 @@ class mip_solver_settings_t { bool mip_scaling = false; presolver_t presolver{presolver_t::Default}; /** - * @brief Determinism mode for MIP solver. + * @brief Determinism mode for MIP solver (bitset). * - * Controls the determinism behavior of the MIP solver: - * - CUOPT_MODE_OPPORTUNISTIC (0): Default mode, allows non-deterministic - * parallelism for better performance - * - CUOPT_MODE_DETERMINISTIC (1): Ensures deterministic results across runs - * at potential cost of performance + * Bitwise OR of CUOPT_DETERMINISM_* flags: + * - CUOPT_DETERMINISM_NONE (0x0): Opportunistic, non-deterministic. + * - CUOPT_DETERMINISM_BB (0x1): Deterministic B&B tree exploration. + * - CUOPT_DETERMINISM_GPU_HEURISTICS (0x2): Deterministic GPU heuristic pipeline. + * - CUOPT_DETERMINISM_FULL (0x3): Both B&B and GPU heuristics deterministic. */ - int determinism_mode = CUOPT_MODE_OPPORTUNISTIC; + int determinism_mode = CUOPT_DETERMINISM_NONE; /** * @brief Random seed for the MIP solver. * diff --git a/cpp/include/cuopt/linear_programming/utilities/internals.hpp b/cpp/include/cuopt/linear_programming/utilities/internals.hpp index fc90dec04f..211b6970af 100644 --- a/cpp/include/cuopt/linear_programming/utilities/internals.hpp +++ b/cpp/include/cuopt/linear_programming/utilities/internals.hpp @@ -8,11 +8,14 @@ #pragma once #include +#include #include #include #include #include +#include + namespace cuopt { namespace internals { @@ -21,7 +24,53 @@ class Callback { virtual ~Callback() {} }; -enum class base_solution_callback_type { GET_SOLUTION, SET_SOLUTION }; +enum class mip_solution_origin_t : uint32_t { + UNKNOWN = CUOPT_MIP_SOLUTION_ORIGIN_UNKNOWN, + BRANCH_AND_BOUND_NODE = CUOPT_MIP_SOLUTION_ORIGIN_BRANCH_AND_BOUND, + BRANCH_AND_BOUND_DIVING = CUOPT_MIP_SOLUTION_ORIGIN_BRANCH_AND_BOUND_DIVING, + FEASIBILITY_JUMP = CUOPT_MIP_SOLUTION_ORIGIN_FEASIBILITY_JUMP, + CPU_FEASIBILITY_JUMP = CUOPT_MIP_SOLUTION_ORIGIN_CPU_FEASIBILITY_JUMP, + LOCAL_SEARCH = CUOPT_MIP_SOLUTION_ORIGIN_LOCAL_SEARCH, + QUICK_FEASIBLE = CUOPT_MIP_SOLUTION_ORIGIN_QUICK_FEASIBLE, + LP_ROUNDING = CUOPT_MIP_SOLUTION_ORIGIN_LP_ROUNDING, + RECOMBINATION = CUOPT_MIP_SOLUTION_ORIGIN_RECOMBINATION, + SUB_MIP = CUOPT_MIP_SOLUTION_ORIGIN_SUB_MIP, + USER_INITIAL = CUOPT_MIP_SOLUTION_ORIGIN_USER_INITIAL, + USER_INJECTED = CUOPT_MIP_SOLUTION_ORIGIN_USER_INJECTED, + RINS = CUOPT_MIP_SOLUTION_ORIGIN_RINS, + PRESOLVE = CUOPT_MIP_SOLUTION_ORIGIN_PRESOLVE, +}; + +constexpr const char* mip_solution_origin_to_string(mip_solution_origin_t origin) +{ + switch (origin) { + case mip_solution_origin_t::UNKNOWN: return "unknown"; + case mip_solution_origin_t::BRANCH_AND_BOUND_NODE: return "branch_and_bound_node"; + case mip_solution_origin_t::BRANCH_AND_BOUND_DIVING: return "branch_and_bound_diving"; + case mip_solution_origin_t::FEASIBILITY_JUMP: return "feasibility_jump"; + case mip_solution_origin_t::LOCAL_SEARCH: return "local_search"; + case mip_solution_origin_t::QUICK_FEASIBLE: return "quick_feasible"; + case mip_solution_origin_t::USER_INITIAL: return "user_initial"; + case mip_solution_origin_t::LP_ROUNDING: return "lp_rounding"; + case mip_solution_origin_t::RECOMBINATION: return "recombination"; + case mip_solution_origin_t::SUB_MIP: return "sub_mip"; + case mip_solution_origin_t::CPU_FEASIBILITY_JUMP: return "cpu_feasibility_jump"; + case mip_solution_origin_t::USER_INJECTED: return "user_injected"; + case mip_solution_origin_t::RINS: return "rins"; + case mip_solution_origin_t::PRESOLVE: return "presolve"; + default: return "unknown"; + } +} + +struct mip_solution_callback_info_t { + uint64_t struct_size{sizeof(mip_solution_callback_info_t)}; + mip_solution_origin_t origin{mip_solution_origin_t::UNKNOWN}; + double work_timestamp{-1.0}; +}; + +// get_solution_ext was added to support passing additional information to the get_solution callback +// without inducing a breaking ABI change +enum class base_solution_callback_type { GET_SOLUTION, GET_SOLUTION_EXT, SET_SOLUTION }; class base_solution_callback_t : public Callback { public: @@ -55,6 +104,19 @@ class get_solution_callback_t : public base_solution_callback_t { } }; +class get_solution_callback_ext_t : public base_solution_callback_t { + public: + virtual void get_solution(void* data, + void* objective_value, + void* solution_bound, + const mip_solution_callback_info_t* callback_info, + void* user_data) = 0; + base_solution_callback_type get_type() const override + { + return base_solution_callback_type::GET_SOLUTION_EXT; + } +}; + class set_solution_callback_t : public base_solution_callback_t { public: virtual void set_solution(void* data, diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index c99210bf34..411db36289 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -7,6 +7,9 @@ set(UTIL_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/utilities/seed_generator.cu ${CMAKE_CURRENT_SOURCE_DIR}/utilities/logger.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utilities/version_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utilities/timestamp_utils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utilities/models/fj_predictor/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utilities/models/fj_predictor/quantize.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utilities/work_unit_predictor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utilities/work_unit_scheduler.cpp) add_subdirectory(pdlp) diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index ba2b63983e..3e189adaba 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -5,6 +5,8 @@ */ /* clang-format on */ +#include + #include #include #include @@ -25,6 +27,7 @@ #include #include +#include #include @@ -42,6 +45,13 @@ #include #include +// uncomment to enable detailed detemrinism logs +#undef CUOPT_DETERMINISM_LOG +#define CUOPT_DETERMINISM_LOG(logger, ...) \ + do { \ + logger.printf(__VA_ARGS__); \ + } while (0) + namespace cuopt::linear_programming::dual_simplex { namespace { @@ -264,6 +274,23 @@ branch_and_bound_t::branch_and_bound_t( dualize_info_t dualize_info; convert_user_problem(original_problem_, settings_, original_lp_, new_slacks_, dualize_info); full_variable_types(original_problem_, original_lp_, var_types_); + assert(new_slacks_.size() == static_cast(original_lp_.num_rows)); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic LP init state: rows=%d cols=%d nnz=%zu slacks=%zu slack_hash=0x%x " + "rhs_hash=0x%x lower_hash=0x%x upper_hash=0x%x Acol_hash=0x%x Arow_hash=0x%x " + "Aval_hash=0x%x\n", + original_lp_.num_rows, + original_lp_.num_cols, + original_lp_.A.x.size(), + new_slacks_.size(), + detail::compute_hash(new_slacks_), + detail::compute_hash(original_lp_.rhs), + detail::compute_hash(original_lp_.lower), + detail::compute_hash(original_lp_.upper), + detail::compute_hash(original_lp_.A.col_start), + detail::compute_hash(original_lp_.A.i), + detail::compute_hash(original_lp_.A.x)); // Check slack #ifdef CHECK_SLACKS @@ -308,19 +335,30 @@ f_t branch_and_bound_t::get_lower_bound() } template -void branch_and_bound_t::report_heuristic(f_t obj) +void branch_and_bound_t::report_heuristic(f_t obj, double work_time) { if (is_running_) { f_t user_obj = compute_user_objective(original_lp_, obj); f_t user_lower = compute_user_objective(original_lp_, get_lower_bound()); std::string user_gap = user_mip_gap(user_obj, user_lower); - - settings_.log.printf( - "H %+13.6e %+10.6e %s %9.2f\n", - user_obj, - user_lower, - user_gap.c_str(), - toc(exploration_stats_.start_time)); + if (settings_.deterministic) { + const double reported_work = work_time >= 0.0 ? work_time : work_unit_context_.current_work(); + settings_.log.printf( + "H %+13.6e %+10.6e %s " + "%9.2f %9.2f\n", + user_obj, + user_lower, + user_gap.c_str(), + reported_work, + toc(exploration_stats_.start_time)); + } else { + settings_.log.printf( + "H %+13.6e %+10.6e %s %9.2f\n", + user_obj, + user_lower, + user_gap.c_str(), + toc(exploration_stats_.start_time)); + } } else { if (solving_root_relaxation_.load()) { f_t user_obj = compute_user_objective(original_lp_, obj); @@ -449,8 +487,45 @@ void branch_and_bound_t::update_user_bound(f_t lower_bound) } template -void branch_and_bound_t::set_new_solution(const std::vector& solution) +void branch_and_bound_t::emit_solution_callback( + std::vector& original_x, + f_t objective, + cuopt::internals::mip_solution_origin_t origin, + double work_timestamp) +{ + cuopt_assert(!settings_.deterministic || work_timestamp >= 0.0, + "work_timestamp must not be negative in deterministic mode"); + if (settings_.new_incumbent_callback != nullptr) { + settings_.log.debug("Publishing incumbent: obj=%g wut=%.6f origin=%s\n", + compute_user_objective(original_lp_, objective), + work_timestamp, + cuopt::internals::mip_solution_origin_to_string(origin)); + cuopt::internals::mip_solution_callback_info_t callback_info{}; + callback_info.origin = origin; + callback_info.work_timestamp = work_timestamp; + settings_.new_incumbent_callback(original_x, objective, callback_info, work_timestamp); + } +} + +template +void branch_and_bound_t::emit_solution_callback_from_crushed( + const std::vector& crushed_solution, + f_t objective, + cuopt::internals::mip_solution_origin_t origin, + double work_timestamp) +{ + if (settings_.new_incumbent_callback == nullptr) { return; } + std::vector original_x; + uncrush_primal_solution(original_problem_, original_lp_, crushed_solution, original_x); + emit_solution_callback(original_x, objective, origin, work_timestamp); +} + +template +void branch_and_bound_t::set_new_solution(const std::vector& solution, + cuopt::internals::mip_solution_origin_t origin) { + cuopt_assert(!settings_.deterministic, "set_new_solution is for opportunistic B&B only"); + mutex_original_lp_.lock(); if (solution.size() != original_problem_.num_cols) { settings_.log.printf( @@ -459,7 +534,9 @@ void branch_and_bound_t::set_new_solution(const std::vector& solu std::vector crushed_solution; crush_primal_solution( original_problem_, original_lp_, solution, new_slacks_, crushed_solution); - f_t obj = compute_objective(original_lp_, crushed_solution); + f_t obj = compute_objective(original_lp_, crushed_solution); + const uint32_t host_hash = detail::compute_hash(solution); + const uint32_t crushed_hash = detail::compute_hash(crushed_solution); mutex_original_lp_.unlock(); bool is_feasible = false; bool attempt_repair = false; @@ -482,8 +559,17 @@ void branch_and_bound_t::set_new_solution(const std::vector& solu mutex_original_lp_.unlock(); mutex_upper_.lock(); if (is_feasible && obj < upper_bound_) { - upper_bound_ = obj; + const f_t previous_upper = upper_bound_; + upper_bound_ = obj; incumbent_.set_incumbent_solution(obj, crushed_solution); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic B&B incumbent update: source=external_direct prev_upper=%.16e " + "new_upper=%.16e obj=%.16e hash=0x%x\n", + previous_upper, + upper_bound_.load(), + obj, + detail::compute_hash(crushed_solution)); } else { attempt_repair = true; constexpr bool verbose = false; @@ -504,52 +590,80 @@ void branch_and_bound_t::set_new_solution(const std::vector& solu if (is_feasible) { report_heuristic(obj); } if (attempt_repair) { mutex_repair_.lock(); - repair_queue_.push_back(solution); + repair_queue_.push_back({solution, origin}); mutex_repair_.unlock(); } } template void branch_and_bound_t::queue_external_solution_deterministic( - const std::vector& solution, double work_unit_ts) + const std::vector& solution, + f_t user_objective, + double work_unit_ts, + cuopt::internals::mip_solution_origin_t origin) { - // In deterministic mode, queue the solution to be processed at the correct work unit timestamp - // This ensures deterministic ordering of solution events + // In deterministic mode, external solutions remain raw until their retirement + // horizon so that feasibility and repair use the retirement LP state. if (solution.size() != original_problem_.num_cols) { settings_.log.printf( "Solution size mismatch %ld %d\n", solution.size(), original_problem_.num_cols); return; } + const double bnb_work_total = work_unit_context_.current_work(); + const uint32_t host_hash = detail::compute_hash(solution); + settings_.log.printf( + "Queueing deterministic external incumbent: obj=%g heur_wut=%.3f bnb_wut=%.3f origin=%s " + "hash=0x%x\n", + user_objective, + work_unit_ts, + bnb_work_total, + cuopt::internals::mip_solution_origin_to_string(origin), + host_hash); mutex_original_lp_.lock(); - std::vector crushed_solution; - crush_primal_solution( - original_problem_, original_lp_, solution, new_slacks_, crushed_solution); - f_t obj = compute_objective(original_lp_, crushed_solution); - - // Validate solution before queueing - f_t primal_err; - f_t bound_err; - i_t num_fractional; - bool is_feasible = check_guess( - original_lp_, settings_, var_types_, crushed_solution, primal_err, bound_err, num_fractional); + const size_t lp_nnz = original_lp_.A.x.size(); + const i_t active_cut_rows = std::max((i_t)0, original_lp_.num_rows - original_problem_.num_rows); + const uint32_t new_slacks_hash = detail::compute_hash(new_slacks_); + const uint32_t rhs_hash = detail::compute_hash(original_lp_.rhs); + const uint32_t lower_hash = detail::compute_hash(original_lp_.lower); + const uint32_t upper_hash = detail::compute_hash(original_lp_.upper); + const uint32_t a_col_hash = detail::compute_hash(original_lp_.A.col_start); + const uint32_t a_row_hash = detail::compute_hash(original_lp_.A.i); + const uint32_t a_val_hash = detail::compute_hash(original_lp_.A.x); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic external crush ctx: wut=%.6f lp_rows=%d lp_cols=%d lp_nnz=%zu " + "active_cut_rows=%d " + "slacks=%zu slack_hash=0x%x rhs_hash=0x%x lower_hash=0x%x upper_hash=0x%x " + "Acol_hash=0x%x Arow_hash=0x%x Aval_hash=0x%x\n", + work_unit_ts, + original_lp_.num_rows, + original_lp_.num_cols, + lp_nnz, + active_cut_rows, + new_slacks_.size(), + new_slacks_hash, + rhs_hash, + lower_hash, + upper_hash, + a_col_hash, + a_row_hash, + a_val_hash); mutex_original_lp_.unlock(); - if (!is_feasible) { - // Queue the uncrushed solution for repair; it will be crushed at - // consumption time so that the crush reflects the current LP state - // (which may have gained slack columns from cuts added after this point). - mutex_repair_.lock(); - repair_queue_.push_back(solution); - mutex_repair_.unlock(); - return; - } - - // Queue the solution with its work unit timestamp mutex_heuristic_queue_.lock(); - heuristic_solution_queue_.push_back({obj, std::move(crushed_solution), 0, -1, 0, work_unit_ts}); + heuristic_solution_queue_.push_back({solution, user_objective, work_unit_ts, origin}); + const size_t heuristic_queue_size = heuristic_solution_queue_.size(); mutex_heuristic_queue_.unlock(); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic external queued_for_retirement: wut=%.6f user_obj=%.16e host_hash=0x%x " + "heur_q=%zu\n", + work_unit_ts, + user_objective, + host_hash, + heuristic_queue_size); } template @@ -610,6 +724,14 @@ bool branch_and_bound_t::repair_solution(const std::vector& edge_ num_fractional, repaired_obj); } + } else { + settings_.log.printf( + "Repair LP failed: status=%s iters=%d time=%.3fs time_limit=%.3f cut_off=%e\n", + dual::status_to_string(lp_status).c_str(), + iter, + toc(lp_start_time), + lp_settings.time_limit, + lp_settings.cut_off); } return feasible; @@ -620,7 +742,7 @@ void branch_and_bound_t::repair_heuristic_solutions() { raft::common::nvtx::range scope("BB::repair_heuristics"); // Check if there are any solutions to repair - std::vector> to_repair; + std::vector to_repair; mutex_repair_.lock(); if (repair_queue_.size() > 0) { to_repair = repair_queue_; @@ -630,7 +752,8 @@ void branch_and_bound_t::repair_heuristic_solutions() if (to_repair.size() > 0) { settings_.log.debug("Attempting to repair %ld injected solutions\n", to_repair.size()); - for (const std::vector& uncrushed_solution : to_repair) { + for (const auto& queued_solution : to_repair) { + const std::vector& uncrushed_solution = queued_solution.solution; std::vector crushed_solution; crush_primal_solution( original_problem_, original_lp_, uncrushed_solution, new_slacks_, crushed_solution); @@ -642,15 +765,23 @@ void branch_and_bound_t::repair_heuristic_solutions() mutex_upper_.lock(); if (repaired_obj < upper_bound_) { - upper_bound_ = repaired_obj; + const f_t previous_upper = upper_bound_; + upper_bound_ = repaired_obj; incumbent_.set_incumbent_solution(repaired_obj, repaired_solution); - report_heuristic(repaired_obj); - - if (settings_.solution_callback != nullptr) { - std::vector original_x; - uncrush_primal_solution(original_problem_, original_lp_, repaired_solution, original_x); - settings_.solution_callback(original_x, repaired_obj); - } + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic B&B incumbent update: source=repair_queue prev_upper=%.16e " + "new_upper=%.16e obj=%.16e hash=0x%x\n", + previous_upper, + upper_bound_.load(), + repaired_obj, + detail::compute_hash(repaired_solution)); + report_heuristic(repaired_obj, queued_solution.work_timestamp); + + emit_solution_callback_from_crushed(repaired_solution, + repaired_obj, + queued_solution.origin, + queued_solution.work_timestamp); } mutex_upper_.unlock(); @@ -680,14 +811,49 @@ void branch_and_bound_t::set_solution_at_root(mip_solution_t compute_user_objective(original_lp_, root_objective_), toc(exploration_stats_.start_time)); - if (settings_.solution_callback != nullptr) { - settings_.solution_callback(solution.x, solution.objective); - } + emit_solution_callback(solution.x, + solution.objective, + cuopt::internals::mip_solution_origin_t::BRANCH_AND_BOUND_NODE, + work_unit_context_.current_work()); if (settings_.heuristic_preemption_callback != nullptr) { settings_.heuristic_preemption_callback(); } } +template +bool branch_and_bound_t::retire_queued_solution( + const queued_external_solution_t& queued_solution, f_t& out_obj, std::vector& out_crushed) +{ + f_t primal_err; + f_t bound_err; + i_t num_fractional; + + mutex_original_lp_.lock(); + crush_primal_solution( + original_problem_, original_lp_, queued_solution.solution, new_slacks_, out_crushed); + out_obj = compute_objective(original_lp_, out_crushed); + bool is_feasible = check_guess( + original_lp_, settings_, var_types_, out_crushed, primal_err, bound_err, num_fractional); + mutex_original_lp_.unlock(); + + if (is_feasible) { return true; } + + std::vector repaired_solution; + f_t repaired_obj; + bool repaired = repair_solution(edge_norms_, out_crushed, repaired_obj, repaired_solution); + if (repaired) { + out_crushed = std::move(repaired_solution); + out_obj = repaired_obj; + return true; + } + + CUOPT_DETERMINISM_LOG(settings_.log, + "Deterministic repair FAILED: wut=%.3f origin=%s\n", + queued_solution.work_timestamp, + cuopt::internals::mip_solution_origin_to_string(queued_solution.origin)); + return false; +} + template void branch_and_bound_t::set_final_solution(mip_solution_t& solution, f_t lower_bound) @@ -761,6 +927,35 @@ void branch_and_bound_t::set_final_solution(mip_solution_t& } } + // Drain any pending heuristic solutions that B&B never got to retire during exploration + // (e.g., root solve consumed the entire budget, or exploration ended between sync horizons). + if (settings_.deterministic) { + const double current_work = work_unit_context_.current_work(); + mutex_heuristic_queue_.lock(); + std::vector pending; + pending.swap(heuristic_solution_queue_); + mutex_heuristic_queue_.unlock(); + + for (const auto& queued_solution : pending) { + if (queued_solution.work_timestamp > current_work) { continue; } + std::vector crushed_solution; + f_t obj; + bool is_feasible = retire_queued_solution(queued_solution, obj, crushed_solution); + + if (is_feasible && obj < upper_bound_) { + upper_bound_ = obj; + incumbent_.set_incumbent_solution(obj, crushed_solution); + settings_.log.printf( + "Late-retired heuristic incumbent: obj=%.6e wut=%.3f origin=%s\n", + compute_user_objective(original_lp_, obj), + queued_solution.work_timestamp, + cuopt::internals::mip_solution_origin_to_string(queued_solution.origin)); + emit_solution_callback_from_crushed( + crushed_solution, obj, queued_solution.origin, queued_solution.work_timestamp); + } + } + } + if (upper_bound_ != inf) { assert(incumbent_.has_incumbent); uncrush_primal_solution(original_problem_, original_lp_, incumbent_.x, solution.x); @@ -769,6 +964,17 @@ void branch_and_bound_t::set_final_solution(mip_solution_t& solution.lower_bound = lower_bound; solution.nodes_explored = exploration_stats_.nodes_explored; solution.simplex_iterations = exploration_stats_.total_lp_iters; + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic B&B final package: status=%d incumbent_obj=%.16e lower_bound=%.16e " + "incumbent_hash=0x%x final_hash=0x%x nodes=%d simplex_iterations=%d\n", + (int)solver_status_.load(), + solution.objective, + solution.lower_bound, + detail::compute_hash(incumbent_.x), + detail::compute_hash(solution.x), + solution.nodes_explored, + solution.simplex_iterations); } template @@ -785,16 +991,29 @@ void branch_and_bound_t::add_feasible_solution(f_t leaf_objective, mutex_upper_.lock(); if (leaf_objective < upper_bound_) { + const f_t previous_upper = upper_bound_; incumbent_.set_incumbent_solution(leaf_objective, leaf_solution); upper_bound_ = leaf_objective; + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic B&B incumbent update: source=leaf prev_upper=%.16e new_upper=%.16e " + "obj=%.16e hash=0x%x depth=%d worker_type=%d\n", + previous_upper, + upper_bound_.load(), + leaf_objective, + detail::compute_hash(leaf_solution), + leaf_depth, + (int)thread_type); report(feasible_solution_symbol(thread_type), leaf_objective, get_lower_bound(), leaf_depth, 0); send_solution = true; } - if (send_solution && settings_.solution_callback != nullptr) { - std::vector original_x; - uncrush_primal_solution(original_problem_, original_lp_, incumbent_.x, original_x); - settings_.solution_callback(original_x, upper_bound_); + if (send_solution) { + emit_solution_callback_from_crushed( + incumbent_.x, + upper_bound_, + cuopt::internals::mip_solution_origin_t::BRANCH_AND_BOUND_NODE, + work_unit_context_.current_work()); } mutex_upper_.unlock(); } @@ -930,6 +1149,23 @@ struct nondeterministic_policy_t : tree_update_policy_t { f_t obj, const std::vector& x) override { + f_t primal_err; + f_t bound_err; + i_t num_fractional; + bool cg = check_guess( + bnb.original_lp_, bnb.settings_, bnb.var_types_, x, primal_err, bound_err, num_fractional); + if (!cg) { + bnb.settings_.log.printf( + "Rejecting infeasible integer solution: node=%d depth=%d " + "obj=%.6e primal_err=%.6e bound_err=%.6e fractional=%d\n", + node->node_id, + node->depth, + obj, + primal_err, + bound_err, + num_fractional); + return; + } bnb.add_feasible_solution(obj, x, node->depth, worker->search_strategy); } @@ -1023,17 +1259,98 @@ struct deterministic_bfs_policy_t const std::vector& x) override { if (obj < this->worker.local_upper_bound) { + f_t primal_err; + f_t bound_err; + i_t num_fractional; + bool cg = check_guess(this->bnb.original_lp_, + this->bnb.settings_, + this->bnb.var_types_, + x, + primal_err, + bound_err, + num_fractional); + if (!cg) { + this->bnb.settings_.log.printf( + "Rejecting infeasible integer solution: worker=%d node=%d depth=%d " + "obj=%.6e primal_err=%.6e bound_err=%.6e fractional=%d\n", + this->worker.worker_id, + node->creation_seq, + node->depth, + obj, + primal_err, + bound_err, + num_fractional); + return; + } + const f_t previous_local_upper = this->worker.local_upper_bound; + const int previous_seq = this->worker.next_solution_seq; this->worker.local_upper_bound = obj; this->worker.integer_solutions.push_back( - {obj, x, node->depth, this->worker.worker_id, this->worker.next_solution_seq++}); + {obj, + x, + node->depth, + this->worker.worker_id, + this->worker.next_solution_seq++, + this->worker.clock, + cuopt::internals::mip_solution_origin_t::BRANCH_AND_BOUND_NODE}); + if (this->bnb.deterministic_current_horizon_ <= + this->bnb.deterministic_horizon_step_ + 1e-9) { + CUOPT_DETERMINISM_LOG( + this->bnb.settings_.log, + "Deterministic BFS local integer queue: horizon=%.6f worker=%d node_id=%d packed=0x%llx " + "path_hash=0x%x depth=%d obj=%.16e sol_hash=0x%x local_upper_before=%.16e " + "local_upper_after=%.16e queue_seq=%d clock=%.6f\n", + this->bnb.deterministic_current_horizon_, + this->worker.worker_id, + node->creation_seq, + (unsigned long long)node->get_id_packed(), + node->compute_path_hash(), + node->depth, + obj, + detail::compute_hash(x), + previous_local_upper, + this->worker.local_upper_bound, + previous_seq, + this->worker.clock); + } } } - branch_variable_t select_branch_variable(mip_node_t*, + branch_variable_t select_branch_variable(mip_node_t* node, const std::vector& fractional, const std::vector& x) override { - i_t var = this->worker.pc_snapshot.variable_selection(fractional, x); + i_t var; + if (this->bnb.settings_.reliability_branching != 0 && + this->worker.nodes_explored_snapshot > 0) { + var = reliable_variable_selection_core(node, + fractional, + x, + this->bnb.settings_, + this->bnb.var_types_, + this->worker.leaf_problem, + this->worker.leaf_edge_norms, + this->worker.basis_factors, + this->worker.basic_list, + this->worker.nonbasic_list, + this->worker.pc_snapshot.sum_down_.data(), + this->worker.pc_snapshot.sum_up_.data(), + this->worker.pc_snapshot.num_down_.data(), + this->worker.pc_snapshot.num_up_.data(), + this->worker.pc_snapshot.n_vars(), + this->worker.pc_snapshot.strong_branching_lp_iter_, + this->worker.local_upper_bound, + (int64_t)this->worker.total_lp_iters_snapshot, + (int64_t)this->worker.nodes_explored_snapshot, + this->bnb.exploration_stats_.start_time, + this->bnb.pc_.reliability_branching_settings, + 1, + nullptr, + nullptr, + nullptr); + } else { + var = this->worker.pc_snapshot.variable_selection(fractional, x); + } auto dir = martin_criteria(x[var], this->bnb.root_relax_soln_.x[var]); return {var, dir}; } @@ -1064,11 +1381,37 @@ struct deterministic_bfs_policy_t node->fractional_val); this->bnb.exploration_stats_.nodes_unexplored += 2; this->worker.enqueue_children_for_plunge(node->get_down_child(), node->get_up_child(), dir); + if (this->bnb.deterministic_current_horizon_ <= + this->bnb.deterministic_horizon_step_ + 1e-9) { + CUOPT_DETERMINISM_LOG( + this->bnb.settings_.log, + "Deterministic BFS branch create: horizon=%.6f worker=%d parent_packed=0x%llx " + "parent_path_hash=0x%x depth=%d branch_var=%d dir=%d frac=%.16e " + "down_packed=0x%llx down_path_hash=0x%x up_packed=0x%llx up_path_hash=0x%x " + "queue_size=%zu local_upper=%.16e\n", + this->bnb.deterministic_current_horizon_, + this->worker.worker_id, + (unsigned long long)node->get_id_packed(), + node->compute_path_hash(), + node->depth, + node->branch_var, + (int)dir, + node->fractional_val, + (unsigned long long)node->get_down_child()->get_id_packed(), + node->get_down_child()->compute_path_hash(), + (unsigned long long)node->get_up_child()->get_id_packed(), + node->get_up_child()->compute_path_hash(), + this->worker.queue_size(), + this->worker.local_upper_bound); + } break; case node_status_t::NUMERICAL: this->worker.record_numerical(node); break; + case node_status_t::PENDING: this->worker.plunge_stack.push_back(node); break; default: break; } - if (status != node_status_t::HAS_CHILDREN) { this->worker.recompute_bounds_and_basis = true; } + if (status != node_status_t::HAS_CHILDREN && status != node_status_t::PENDING) { + this->worker.recompute_bounds_and_basis = true; + } } void on_numerical_issue(mip_node_t* node) override @@ -1099,8 +1442,55 @@ struct deterministic_diving_policy_t const std::vector& x) override { if (obj < this->worker.local_upper_bound) { + f_t primal_err; + f_t bound_err; + i_t num_fractional; + bool cg = check_guess(this->bnb.original_lp_, + this->bnb.settings_, + this->bnb.var_types_, + x, + primal_err, + bound_err, + num_fractional); + if (!cg) { + this->bnb.settings_.log.printf( + "Rejecting infeasible diving integer solution: worker=%d node=%d depth=%d " + "obj=%.6e primal_err=%.6e bound_err=%.6e fractional=%d\n", + this->worker.worker_id, + node->creation_seq, + node->depth, + obj, + primal_err, + bound_err, + num_fractional); + return; + } + const f_t previous_local_upper = this->worker.local_upper_bound; + const int previous_seq = this->worker.next_solution_seq; this->worker.local_upper_bound = obj; this->worker.queue_integer_solution(obj, x, node->depth); + if (this->bnb.deterministic_current_horizon_ <= + this->bnb.deterministic_horizon_step_ + 1e-9) { + CUOPT_DETERMINISM_LOG( + this->bnb.settings_.log, + "Deterministic diving local integer queue: horizon=%.6f worker=%d node_id=%d " + "packed=0x%llx " + "path_hash=0x%x depth=%d obj=%.16e sol_hash=0x%x local_upper_before=%.16e " + "local_upper_after=%.16e queue_seq=%d clock=%.6f type=%d\n", + this->bnb.deterministic_current_horizon_, + this->worker.worker_id, + node->creation_seq, + (unsigned long long)node->get_id_packed(), + node->compute_path_hash(), + node->depth, + obj, + detail::compute_hash(x), + previous_local_upper, + this->worker.local_upper_bound, + previous_seq, + this->worker.clock, + (int)this->worker.diving_type); + } } } @@ -1950,6 +2340,15 @@ lp_status_t branch_and_bound_t::solve_root_relaxation( settings_.log.printf("\n"); is_root_solution_set = true; + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic root flag set: root_status=%d root_obj=%.16e recomputed_root_obj=%.16e " + "callback_flag=%d x_hash=0x%x\n", + (int)root_status, + root_objective_, + compute_objective(original_lp_, root_relax_soln_.x), + (int)root_crossover_solution_set_.load(std::memory_order_acquire), + detail::compute_hash(root_relax_soln_.x)); return root_status; } @@ -1958,6 +2357,18 @@ template mip_status_t branch_and_bound_t::solve(mip_solution_t& solution) { raft::common::nvtx::range scope("BB::solve"); + auto exploration_signal_guard = cuopt::scope_guard([this]() { + if (!exploration_started_.load()) { + std::lock_guard lock(exploration_started_mutex_); + exploration_started_ = true; + exploration_started_cv_.notify_all(); + } + }); + auto heuristic_preemption_guard = cuopt::scope_guard([this]() { + if (settings_.heuristic_preemption_callback != nullptr) { + settings_.heuristic_preemption_callback(); + } + }); logger_t log; log.log = false; @@ -1969,6 +2380,26 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut exploration_stats_.nodes_explored = 0; original_lp_.A.to_compressed_row(Arow_); + work_unit_scheduler_t* saved_scheduler = work_unit_context_.scheduler; + if (settings_.deterministic) { + work_unit_context_.deterministic = true; + cuopt_assert(settings_.bnb_work_unit_scale > 0.0, "B&B work-unit scale must be positive"); + if (settings_.gpu_heur_wait_for_exploration) { + // Scale=0 during pre-exploration: root LP/cuts/SB don't advance the deterministic timeline. + // GPU heuristics start after exploration, so both timelines begin at 0 together. + work_unit_context_.work_unit_scale = 0.0; + } else { + // GPU heuristics race with B&B pre-exploration, so B&B work must advance normally. + work_unit_context_.work_unit_scale = settings_.bnb_work_unit_scale; + } + + // Detach the scheduler during the serial root/cuts/SB phase. + // record_work_sync_on_horizon still accumulates global_work_units_elapsed, + // but avoids scheduler->on_work_recorded whose OMP directives + // perturb FP state in a single-thread context. + work_unit_context_.scheduler = nullptr; + } + settings_.log.printf("Reduced cost strengthening enabled: %d\n", settings_.reduced_cost_strengthening); @@ -1987,15 +2418,25 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut if (feasible) { const f_t computed_obj = compute_objective(original_lp_, crushed_guess); mutex_upper_.lock(); + const f_t previous_upper = upper_bound_; incumbent_.set_incumbent_solution(computed_obj, crushed_guess); upper_bound_ = computed_obj; + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic B&B incumbent update: source=initial_guess prev_upper=%.16e " + "new_upper=%.16e obj=%.16e hash=0x%x\n", + previous_upper, + upper_bound_.load(), + computed_obj, + detail::compute_hash(crushed_guess)); mutex_upper_.unlock(); } } root_relax_soln_.resize(original_lp_.num_rows, original_lp_.num_cols); - if (settings_.clique_cuts != 0 && clique_table_ == nullptr) { + // TODO: ensure clique tables work well w/ determinism + if (settings_.clique_cuts != 0 && clique_table_ == nullptr && !settings_.deterministic) { signal_extend_cliques_.store(false, std::memory_order_release); typename ::cuopt::linear_programming::mip_solver_settings_t::tolerances_t tolerances_for_clique{}; @@ -2045,7 +2486,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut basic_list, nonbasic_list, root_vstatus_, - edge_norms_); + edge_norms_, + &work_unit_context_); } else { settings_.log.printf("\nSolving LP root relaxation in concurrent mode\n"); root_status = solve_root_relaxation(lp_settings, @@ -2059,6 +2501,10 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut solving_root_relaxation_ = false; exploration_stats_.total_lp_iters = root_relax_soln_.iterations; exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); + CUOPT_DETERMINISM_LOG(settings_.log, + "Post-root-LP work: %.16e iters=%d\n", + work_unit_context_.current_work(), + root_relax_soln_.iterations); auto finish_clique_thread = [this]() { if (clique_table_future_.valid()) { @@ -2104,7 +2550,18 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut assert(root_vstatus_.size() == original_lp_.num_cols); set_uninitialized_steepest_edge_norms(original_lp_, basic_list, edge_norms_); - root_objective_ = compute_objective(original_lp_, root_relax_soln_.x); + { + const f_t previous_root_objective = root_objective_; + root_objective_ = compute_objective(original_lp_, root_relax_soln_.x); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic root objective assign: source=post_root_solve old=%.16e new=%.16e " + "x_hash=0x%x obj_hash=0x%x\n", + previous_root_objective, + root_objective_, + detail::compute_hash(root_relax_soln_.x), + detail::compute_hash(original_lp_.objective)); + } if (settings_.set_simplex_solution_callback != nullptr) { std::vector original_x; @@ -2182,6 +2639,12 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } #endif + if (toc(exploration_stats_.start_time) > settings_.time_limit) { + solver_status_ = mip_status_t::TIME_LIMIT; + set_final_solution(solution, root_objective_); + return solver_status_; + } + // Generate cuts and add them to the cut pool f_t cut_start_time = tic(); bool problem_feasible = cut_generation.generate_cuts(original_lp_, @@ -2242,7 +2705,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut cut_pool_size = cut_pool.pool_size(); // Resolve the LP with the new cuts - settings_.log.debug( + settings_.log.printf( "Solving LP with %d cuts (%d cut nonzeros). Cuts in pool %d. Total constraints %d\n", num_cuts, cuts_to_add.row_start[cuts_to_add.m], @@ -2322,8 +2785,30 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut i_t iter = 0; bool initialize_basis = false; lp_settings.concurrent_halt = NULL; - f_t dual_phase2_start_time = tic(); - dual::status_t cut_status = dual_phase2_with_advanced_basis(2, + CUOPT_DETERMINISM_LOG(settings_.log, + "Cut loop LP warm-start: pass=%d rows=%d cols=%d " + "lower_hash=0x%x upper_hash=0x%x " + "x_hash=0x%x y_hash=0x%x z_hash=0x%x " + "basic_hash=0x%x nonbasic_hash=0x%x " + "vstatus_hash=0x%x edge_norms_hash=0x%x " + "cut_off=%.16e work_limit=%.16e time_limit=%.16e\n", + cut_pass, + original_lp_.num_rows, + original_lp_.num_cols, + detail::compute_hash(original_lp_.lower), + detail::compute_hash(original_lp_.upper), + detail::compute_hash(root_relax_soln_.x), + detail::compute_hash(root_relax_soln_.y), + detail::compute_hash(root_relax_soln_.z), + detail::compute_hash(basic_list), + detail::compute_hash(nonbasic_list), + detail::compute_hash(root_vstatus_), + detail::compute_hash(edge_norms_), + lp_settings.cut_off, + lp_settings.work_limit, + lp_settings.time_limit); + f_t dual_phase2_start_time = tic(); + dual::status_t cut_status = dual_phase2_with_advanced_basis(2, 0, initialize_basis, exploration_stats_.start_time, @@ -2335,7 +2820,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut nonbasic_list, root_relax_soln_, iter, - edge_norms_); + edge_norms_, + &work_unit_context_); exploration_stats_.total_lp_iters += iter; f_t dual_phase2_time = toc(dual_phase2_start_time); if (dual_phase2_time > 1.0) { @@ -2347,6 +2833,12 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut return solver_status_; } + if (cut_status == dual::status_t::WORK_LIMIT) { + solver_status_ = mip_status_t::WORK_LIMIT; + set_final_solution(solution, root_objective_); + return solver_status_; + } + if (cut_status != dual::status_t::OPTIMAL) { settings_.log.printf("Numerical issue at root node. Resolving from scratch\n"); lp_status_t scratch_status = @@ -2358,12 +2850,25 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut basic_list, nonbasic_list, root_vstatus_, - edge_norms_); + edge_norms_, + &work_unit_context_); if (scratch_status == lp_status_t::OPTIMAL) { // We recovered cut_status = convert_lp_status_to_dual_status(scratch_status); exploration_stats_.total_lp_iters += root_relax_soln_.iterations; - root_objective_ = compute_objective(original_lp_, root_relax_soln_.x); + { + const f_t previous_root_objective = root_objective_; + root_objective_ = compute_objective(original_lp_, root_relax_soln_.x); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic root objective assign: source=cut_lp_scratch old=%.16e new=%.16e " + "pass=%d x_hash=0x%x obj_hash=0x%x\n", + previous_root_objective, + root_objective_, + cut_pass, + detail::compute_hash(root_relax_soln_.x), + detail::compute_hash(original_lp_.objective)); + } } else { settings_.log.printf("Cut status %s\n", dual::status_to_string(cut_status).c_str()); #ifdef WRITE_CUT_INFEASIBLE_MPS @@ -2376,6 +2881,38 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut f_t remove_cuts_start_time = tic(); mutex_original_lp_.lock(); + assert(new_slacks_.size() == static_cast(original_lp_.num_rows)); + const f_t root_objective_before_remove = root_objective_; + const f_t root_objective_before_remove_recomputed = + compute_objective(original_lp_, root_relax_soln_.x); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic root LP before remove_cuts: pass=%d fractional=%d rows=%d cols=%d " + "nnz=%zu original_rows=%d active_cut_rows=%d slacks=%zu slack_hash=0x%x rhs_hash=0x%x " + "lower_hash=0x%x upper_hash=0x%x obj_hash=0x%x Acol_hash=0x%x Arow_hash=0x%x " + "Aval_hash=0x%x root_obj_before_remove=%.16e root_obj_before_remove_recomputed=%.16e " + "root_obj_before_remove_delta=%.16e callback_flag=%d root_flag=%d\n", + cut_pass, + num_fractional, + original_lp_.num_rows, + original_lp_.num_cols, + original_lp_.A.x.size(), + original_rows, + std::max((i_t)0, original_lp_.num_rows - original_rows), + new_slacks_.size(), + detail::compute_hash(new_slacks_), + detail::compute_hash(original_lp_.rhs), + detail::compute_hash(original_lp_.lower), + detail::compute_hash(original_lp_.upper), + detail::compute_hash(original_lp_.objective), + detail::compute_hash(original_lp_.A.col_start), + detail::compute_hash(original_lp_.A.i), + detail::compute_hash(original_lp_.A.x), + root_objective_before_remove, + root_objective_before_remove_recomputed, + root_objective_before_remove_recomputed - root_objective_before_remove, + (int)root_crossover_solution_set_.load(std::memory_order_acquire), + (int)is_root_solution_set); remove_cuts(original_lp_, settings_, exploration_stats_.start_time, @@ -2392,6 +2929,34 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut nonbasic_list, basis_update); variable_bounds.resize(original_lp_.num_cols); + assert(new_slacks_.size() == static_cast(original_lp_.num_rows)); + const f_t root_objective_after_remove = compute_objective(original_lp_, root_relax_soln_.x); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic root LP after remove_cuts: pass=%d fractional=%d rows=%d cols=%d " + "nnz=%zu original_rows=%d active_cut_rows=%d slacks=%zu slack_hash=0x%x rhs_hash=0x%x " + "lower_hash=0x%x upper_hash=0x%x obj_hash=0x%x Acol_hash=0x%x Arow_hash=0x%x " + "Aval_hash=0x%x root_obj_before_remove=%.16e root_obj_after_remove=%.16e " + "root_obj_remove_delta=%.16e\n", + cut_pass, + num_fractional, + original_lp_.num_rows, + original_lp_.num_cols, + original_lp_.A.x.size(), + original_rows, + std::max((i_t)0, original_lp_.num_rows - original_rows), + new_slacks_.size(), + detail::compute_hash(new_slacks_), + detail::compute_hash(original_lp_.rhs), + detail::compute_hash(original_lp_.lower), + detail::compute_hash(original_lp_.upper), + detail::compute_hash(original_lp_.objective), + detail::compute_hash(original_lp_.A.col_start), + detail::compute_hash(original_lp_.A.i), + detail::compute_hash(original_lp_.A.x), + root_objective_before_remove, + root_objective_after_remove, + root_objective_after_remove - root_objective_before_remove); mutex_original_lp_.unlock(); f_t remove_cuts_time = toc(remove_cuts_start_time); if (remove_cuts_time > 1.0) { @@ -2399,11 +2964,50 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } fractional.clear(); num_fractional = fractional_variables(settings_, root_relax_soln_.x, var_types_, fractional); + assert(root_relax_soln_.x.size() == static_cast(original_lp_.num_cols)); + assert(root_relax_soln_.y.size() == static_cast(original_lp_.num_rows)); + assert(root_relax_soln_.z.size() == static_cast(original_lp_.num_cols)); + assert(basic_list.size() == static_cast(original_lp_.num_rows)); + assert(nonbasic_list.size() == + static_cast(original_lp_.num_cols - original_lp_.num_rows)); + assert(root_vstatus_.size() == static_cast(original_lp_.num_cols)); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic root pass state: pass=%d root_obj=%.16e num_fractional=%d rel_gap=%.16e " + "abs_gap=%.16e x_hash=0x%x y_hash=0x%x z_hash=0x%x basic_hash=0x%x nonbasic_hash=0x%x " + "vstatus_hash=0x%x edge_norms_hash=0x%x root_obj_after_remove=%.16e " + "root_obj_remove_delta=%.16e root_obj_member_delta=%.16e callback_flag=%d root_flag=%d\n", + cut_pass, + root_objective_, + num_fractional, + user_relative_gap(original_lp_, upper_bound_.load(), root_objective_), + upper_bound_.load() - root_objective_, + detail::compute_hash(root_relax_soln_.x), + detail::compute_hash(root_relax_soln_.y), + detail::compute_hash(root_relax_soln_.z), + detail::compute_hash(basic_list), + detail::compute_hash(nonbasic_list), + detail::compute_hash(root_vstatus_), + detail::compute_hash(edge_norms_), + root_objective_after_remove, + root_objective_after_remove - root_objective_, + root_objective_ - root_objective_after_remove, + (int)root_crossover_solution_set_.load(std::memory_order_acquire), + (int)is_root_solution_set); if (num_fractional == 0) { - upper_bound_ = root_objective_; + const f_t previous_upper = upper_bound_; + upper_bound_ = root_objective_; mutex_upper_.lock(); incumbent_.set_incumbent_solution(root_objective_, root_relax_soln_.x); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic B&B incumbent update: source=root_integral_pass prev_upper=%.16e " + "new_upper=%.16e obj=%.16e hash=0x%x\n", + previous_upper, + upper_bound_.load(), + root_objective_, + detail::compute_hash(root_relax_soln_.x)); mutex_upper_.unlock(); } f_t obj = upper_bound_.load(); @@ -2428,6 +3032,16 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_relax_objective); break; } + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic root pass continue: pass=%d change=%.16e threshold=%.16e last_obj=%.16e " + "root_relax_obj=%.16e root_obj=%.16e\n", + cut_pass, + change_in_objective, + factor * std::max(min_objective, std::abs(root_relax_objective)), + last_objective, + root_relax_objective, + root_objective_); last_objective = root_objective_; } } @@ -2458,7 +3072,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_objective_, root_vstatus_, edge_norms_, - pc_); + pc_, + &work_unit_context_); } if (toc(exploration_stats_.start_time) > settings_.time_limit) { @@ -2539,6 +3154,15 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut calculate_variable_locks(original_lp_, var_up_locks_, var_down_locks_); } if (settings_.deterministic) { + pre_exploration_work_ = work_unit_context_.current_work(); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Pre-exploration work breakdown: total=%.16e scale=%.6f deterministic=%d\n", + pre_exploration_work_, + work_unit_context_.work_unit_scale, + (int)work_unit_context_.deterministic); + work_unit_context_.scheduler = saved_scheduler; + work_unit_context_.work_unit_scale = settings_.bnb_work_unit_scale; settings_.log.printf( " | Explored | Unexplored | Objective | Bound | IntInf | Depth | Iter/Node " "| Gap | Work | Time |\n"); @@ -2548,6 +3172,12 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut "| Gap | Time |\n"); } + { + std::lock_guard lock(exploration_started_mutex_); + exploration_started_ = true; + } + exploration_started_cv_.notify_all(); + if (settings_.deterministic) { run_deterministic_coordinator(Arow_); } else if (settings_.num_threads > 1) { @@ -2720,8 +3350,7 @@ void branch_and_bound_t::run_deterministic_coordinator(const csr_matri deterministic_horizon_step_ = 0.50; - // Compute worker counts using the same formula as reliability-branching scheduler - const i_t num_workers = 2 * settings_.num_threads; + const i_t num_workers = settings_.num_threads; std::vector search_strategies = get_search_strategies(settings_.diving_settings); std::array max_num_workers = @@ -2734,7 +3363,7 @@ void branch_and_bound_t::run_deterministic_coordinator(const csr_matri } deterministic_mode_enabled_ = true; - deterministic_current_horizon_ = deterministic_horizon_step_; + deterministic_current_horizon_ = pre_exploration_work_ + deterministic_horizon_step_; deterministic_horizon_number_ = 0; deterministic_global_termination_status_ = mip_status_t::UNSET; @@ -2766,10 +3395,12 @@ void branch_and_bound_t::run_deterministic_coordinator(const csr_matri scoped_context_registrations_t context_registrations(*deterministic_scheduler_); for (auto& worker : *deterministic_workers_) { + worker.clock = pre_exploration_work_; context_registrations.add(worker.work_context); } if (deterministic_diving_workers_) { for (auto& worker : *deterministic_diving_workers_) { + worker.clock = pre_exploration_work_; context_registrations.add(worker.work_context); } } @@ -2777,8 +3408,9 @@ void branch_and_bound_t::run_deterministic_coordinator(const csr_matri int actual_diving_workers = deterministic_diving_workers_ ? (int)deterministic_diving_workers_->size() : 0; settings_.log.printf( - "Deterministic Mode: %d BFS workers + %d diving workers, horizon step = %.2f work " - "units\n", + "Deterministic Mode: %d total threads split as %d BFS workers + %d diving workers, " + "horizon step = %.2f work units\n", + num_workers, num_bfs_workers, actual_diving_workers, deterministic_horizon_step_); @@ -2911,11 +3543,16 @@ void branch_and_bound_t::run_deterministic_bfs_loop( bool is_child = (node->parent == worker.last_solved_node); worker.recompute_bounds_and_basis = !is_child; - node_status_t status = solve_node_deterministic(worker, node, search_tree); - worker.last_solved_node = node; + node_status_t status = solve_node_deterministic(worker, node, search_tree); + worker.current_node = nullptr; - worker.current_node = nullptr; - continue; + if (status == node_status_t::PENDING) { + // LP didn't finish (TIME_LIMIT/WORK_LIMIT). Node was re-enqueued by on_node_completed. + // Fall through to sync barrier instead of immediately retrying. + } else { + worker.last_solved_node = node; + continue; + } } // No work - advance to sync point to participate in barrier @@ -2939,26 +3576,57 @@ void branch_and_bound_t::deterministic_sync_callback() total_producer_wait_time_ += wait_time; max_producer_wait_time_ = std::max(max_producer_wait_time_, wait_time); ++producer_wait_count_; + if (wait_time > 0.01) { + settings_.log.printf( + "Producer sync wait: %.3fs at horizon %.2f (cumulative: %.3fs, count: %d)\n", + wait_time, + horizon_end, + total_producer_wait_time_, + producer_wait_count_); + } - work_unit_context_.global_work_units_elapsed = horizon_end; - - bb_event_batch_t all_events = deterministic_workers_->collect_and_sort_events(); + work_unit_context_.set_current_work(horizon_end, false); - deterministic_sort_replay_events(all_events); + { + std::string worker_clocks_str; + for (const auto& w : *deterministic_workers_) { + worker_clocks_str += std::to_string(w.worker_id) + ":" + std::to_string(w.clock) + "/" + + std::to_string(w.integer_solutions.size()) + " "; + } + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic sync #%d: horizon=%.6f pre_expl=%.6f heur_q=%zu workers=[%s]\n", + deterministic_horizon_number_, + deterministic_current_horizon_, + pre_exploration_work_, + heuristic_solution_queue_.size(), + worker_clocks_str.c_str()); + } - // deterministic_prune_worker_nodes_vs_incumbent(); + bb_event_batch_t all_events = deterministic_workers_->collect_and_sort_events(); - deterministic_collect_diving_solutions_and_update_pseudocosts(); + std::vector::deterministic_replay_solution_t> + replay_solutions; + deterministic_collect_worker_solutions( + *deterministic_workers_, + [](const deterministic_bfs_worker_pool_t&, int) { + return search_strategy_t::BEST_FIRST; + }, + replay_solutions); + deterministic_collect_diving_solutions_and_update_pseudocosts(replay_solutions); - for (auto& worker : *deterministic_workers_) { - worker.integer_solutions.clear(); - } if (deterministic_diving_workers_) { for (auto& worker : *deterministic_diving_workers_) { - worker.integer_solutions.clear(); + i_t delta = worker.total_nodes_explored - worker.nodes_explored_last_sync; + worker.nodes_explored_last_sync = worker.total_nodes_explored; + exploration_stats_.nodes_explored += delta; } } + deterministic_sort_replay_events(all_events, replay_solutions); + + // deterministic_prune_worker_nodes_vs_incumbent(); + deterministic_populate_diving_heap(); deterministic_assign_diving_nodes(); @@ -3102,9 +3770,15 @@ node_status_t branch_and_bound_t::solve_node_deterministic( simplex_solver_settings_t lp_settings = settings_; lp_settings.set_log(false); - lp_settings.cut_off = worker.local_upper_bound + settings_.dual_tol; + if (original_lp_.objective_is_integral) { + lp_settings.cut_off = + std::ceil(worker.local_upper_bound - settings_.integer_tol) + settings_.dual_tol; + } else { + lp_settings.cut_off = worker.local_upper_bound + settings_.dual_tol; + } lp_settings.inside_mip = 2; lp_settings.time_limit = remaining_time; + lp_settings.work_limit = std::numeric_limits::infinity(); lp_settings.scale_columns = false; bool feasible = true; @@ -3134,7 +3808,7 @@ node_status_t branch_and_bound_t::solve_node_deterministic( std::vector& leaf_vstatus = node_ptr->vstatus; i_t node_iter = 0; f_t lp_start_time = tic(); - std::vector leaf_edge_norms = edge_norms_; + worker.leaf_edge_norms = edge_norms_; dual::status_t lp_status = dual_phase2_with_advanced_basis(2, 0, @@ -3148,7 +3822,7 @@ node_status_t branch_and_bound_t::solve_node_deterministic( worker.nonbasic_list, worker.leaf_solution, node_iter, - leaf_edge_norms, + worker.leaf_edge_norms, &worker.work_context); if (lp_status == dual::status_t::NUMERICAL) { @@ -3161,18 +3835,24 @@ node_status_t branch_and_bound_t::solve_node_deterministic( worker.basic_list, worker.nonbasic_list, leaf_vstatus, - leaf_edge_norms, + worker.leaf_edge_norms, &worker.work_context); lp_status = convert_lp_status_to_dual_status(second_status); } + double clock_before = worker.clock; double work_performed = worker.work_context.global_work_units_elapsed - work_units_at_start; worker.clock += work_performed; exploration_stats_.total_lp_solve_time += toc(lp_start_time); exploration_stats_.total_lp_iters += node_iter; - ++exploration_stats_.nodes_explored; - --exploration_stats_.nodes_unexplored; + + bool lp_conclusive = + (lp_status != dual::status_t::TIME_LIMIT && lp_status != dual::status_t::WORK_LIMIT); + if (lp_conclusive) { + ++exploration_stats_.nodes_explored; + --exploration_stats_.nodes_unexplored; + } deterministic_bfs_policy_t policy{*this, worker}; auto [status, round_dir] = update_tree_impl(node_ptr, search_tree, &worker, lp_status, policy); @@ -3182,58 +3862,17 @@ node_status_t branch_and_bound_t::solve_node_deterministic( template template -void branch_and_bound_t::deterministic_process_worker_solutions( - PoolT& pool, WorkerTypeGetter get_worker_type) +void branch_and_bound_t::deterministic_collect_worker_solutions( + PoolT& pool, + WorkerTypeGetter get_worker_type, + std::vector::deterministic_replay_solution_t>& + replay_solutions) { - std::vector*> all_solutions; for (auto& worker : pool) { for (auto& sol : worker.integer_solutions) { - all_solutions.push_back(&sol); + const search_strategy_t strategy = get_worker_type(pool, sol.worker_id); + replay_solutions.push_back({std::move(sol), strategy}); } - } - - // relies on queued_integer_solution_t's operator< - // sorts based on objective first, then the tuple - std::sort(all_solutions.begin(), - all_solutions.end(), - [](const queued_integer_solution_t* a, - const queued_integer_solution_t* b) { return *a < *b; }); - - f_t deterministic_lower = deterministic_compute_lower_bound(); - f_t current_upper = upper_bound_.load(); - - for (const auto* sol : all_solutions) { - if (sol->objective < current_upper) { - f_t user_obj = compute_user_objective(original_lp_, sol->objective); - f_t user_lower = compute_user_objective(original_lp_, deterministic_lower); - i_t nodes_explored = exploration_stats_.nodes_explored.load(); - i_t nodes_unexplored = exploration_stats_.nodes_unexplored.load(); - - search_strategy_t worker_type = get_worker_type(pool, sol->worker_id); - report(feasible_solution_symbol(worker_type), - sol->objective, - deterministic_lower, - sol->depth, - 0, - deterministic_current_horizon_); - - bool improved = false; - if (sol->objective < upper_bound_) { - upper_bound_ = sol->objective; - incumbent_.set_incumbent_solution(sol->objective, sol->solution); - current_upper = sol->objective; - improved = true; - } - - if (improved && settings_.solution_callback != nullptr) { - std::vector original_x; - uncrush_primal_solution(original_problem_, original_lp_, sol->solution, original_x); - settings_.solution_callback(original_x, sol->objective); - } - } - } - - for (auto& worker : pool) { worker.integer_solutions.clear(); } } @@ -3243,12 +3882,17 @@ template void branch_and_bound_t::deterministic_merge_pseudo_cost_updates(PoolT& pool) { std::vector> all_pc_updates; + int64_t sb_iter_delta = 0; for (auto& worker : pool) { auto updates = worker.pc_snapshot.take_updates(); all_pc_updates.insert(all_pc_updates.end(), updates.begin(), updates.end()); + int64_t snapshot_sb = worker.pc_snapshot.strong_branching_lp_iter_; + int64_t base_sb = pc_.strong_branching_lp_iter.load(); + if (snapshot_sb > base_sb) { sb_iter_delta += snapshot_sb - base_sb; } } std::sort(all_pc_updates.begin(), all_pc_updates.end()); pc_.merge_updates(all_pc_updates); + if (sb_iter_delta > 0) { pc_.strong_branching_lp_iter += sb_iter_delta; } } template @@ -3259,6 +3903,7 @@ void branch_and_bound_t::deterministic_broadcast_snapshots( deterministic_snapshot_t snap; snap.upper_bound = upper_bound_.load(); snap.total_lp_iters = exploration_stats_.total_lp_iters.load(); + snap.nodes_explored = exploration_stats_.nodes_explored.load(); snap.incumbent = incumbent_snapshot; snap.pc_snapshot = pc_.create_snapshot(); @@ -3269,91 +3914,153 @@ void branch_and_bound_t::deterministic_broadcast_snapshots( template void branch_and_bound_t::deterministic_sort_replay_events( - const bb_event_batch_t& events) + const bb_event_batch_t& events, + std::vector::deterministic_replay_solution_t>& + replay_solutions) { - // Infeasible solutions from GPU heuristics are queued for repair; process them now + // Retire external solutions that have reached the current horizon. Feasibility + // classification and repair happen only here in deterministic mode. { - std::vector> to_repair; - // TODO: support repair queue in deterministic mode - // mutex_repair_.lock(); - // if (repair_queue_.size() > 0) { - // to_repair = repair_queue_; - // repair_queue_.clear(); - // } - // mutex_repair_.unlock(); - - std::sort(to_repair.begin(), - to_repair.end(), - [](const std::vector& a, const std::vector& b) { return a < b; }); - - if (to_repair.size() > 0) { - settings_.log.debug("Deterministic sync: Attempting to repair %ld injected solutions\n", - to_repair.size()); - for (const std::vector& uncrushed_solution : to_repair) { + std::vector due_solutions; + mutex_heuristic_queue_.lock(); + { + std::vector future_solutions; + for (auto& sol : heuristic_solution_queue_) { + if (sol.work_timestamp < deterministic_current_horizon_) { + due_solutions.push_back(std::move(sol)); + } else { + future_solutions.push_back(std::move(sol)); + } + } + heuristic_solution_queue_ = std::move(future_solutions); + } + mutex_heuristic_queue_.unlock(); + + std::sort(due_solutions.begin(), + due_solutions.end(), + [](const queued_external_solution_t& a, const queued_external_solution_t& b) { + if (a.work_timestamp != b.work_timestamp) { + return a.work_timestamp < b.work_timestamp; + } + if (a.user_objective != b.user_objective) { + return a.user_objective < b.user_objective; + } + if (a.origin != b.origin) { return a.origin < b.origin; } + return a.solution < b.solution; + }); + + if (!due_solutions.empty() || !heuristic_solution_queue_.empty()) { + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic sync retire: horizon=%.6f due=%zu future=%zu pre_expl=%.6f\n", + deterministic_current_horizon_, + due_solutions.size(), + heuristic_solution_queue_.size(), + pre_exploration_work_); + for (size_t i = 0; i < due_solutions.size(); ++i) { + CUOPT_DETERMINISM_LOG( + settings_.log, + " due[%zu]: wut=%.6f obj=%g origin=%s\n", + i, + due_solutions[i].work_timestamp, + due_solutions[i].user_objective, + cuopt::internals::mip_solution_origin_to_string(due_solutions[i].origin)); + } + } + if (!due_solutions.empty()) { + CUOPT_DETERMINISM_LOG(settings_.log, + "Deterministic sync: retiring %ld external solutions\n", + due_solutions.size()); + for (const auto& queued_solution : due_solutions) { std::vector crushed_solution; - crush_primal_solution( - original_problem_, original_lp_, uncrushed_solution, new_slacks_, crushed_solution); - std::vector repaired_solution; - f_t repaired_obj; - bool success = - repair_solution(edge_norms_, crushed_solution, repaired_obj, repaired_solution); - if (success) { - // Queue repaired solution with work unit timestamp (...workstamp?) - mutex_heuristic_queue_.lock(); - heuristic_solution_queue_.push_back( - {repaired_obj, std::move(repaired_solution), 0, -1, 0, deterministic_current_horizon_}); - mutex_heuristic_queue_.unlock(); + f_t obj; + bool is_feasible = retire_queued_solution(queued_solution, obj, crushed_solution); + if (is_feasible) { + replay_solutions.push_back({{obj, + std::move(crushed_solution), + 0, + -1, + 0, + queued_solution.work_timestamp, + queued_solution.origin}, + search_strategy_t::BEST_FIRST}); } } } } + if (!replay_solutions.empty() || !heuristic_solution_queue_.empty()) { + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic replay extract: horizon=%.6f now=%zu future=%zu upper=%.16e\n", + deterministic_current_horizon_, + replay_solutions.size(), + heuristic_solution_queue_.size(), + upper_bound_.load()); + } - // Extract heuristic solutions, keeping future solutions for next horizon - // Use deterministic_current_horizon_ as the upper bound (horizon_end) - std::vector> heuristic_solutions; - mutex_heuristic_queue_.lock(); - { - std::vector> future_solutions; - for (auto& sol : heuristic_solution_queue_) { - if (sol.work_timestamp < deterministic_current_horizon_) { - heuristic_solutions.push_back(std::move(sol)); - } else { - future_solutions.push_back(std::move(sol)); - } + // Sort the full replay stream by work unit timestamp, with stable deterministic tie-breakers. + std::sort(replay_solutions.begin(), replay_solutions.end(), [](const auto& a, const auto& b) { + if (a.solution.work_timestamp != b.solution.work_timestamp) { + return a.solution.work_timestamp < b.solution.work_timestamp; } - heuristic_solution_queue_ = std::move(future_solutions); - } - mutex_heuristic_queue_.unlock(); + if (a.solution.objective != b.solution.objective) { + return a.solution.objective < b.solution.objective; + } + if (a.solution.origin != b.solution.origin) { return a.solution.origin < b.solution.origin; } + if (a.solution.worker_id != b.solution.worker_id) { + return a.solution.worker_id < b.solution.worker_id; + } + if (a.solution.sequence_id != b.solution.sequence_id) { + return a.solution.sequence_id < b.solution.sequence_id; + } + return a.solution.solution < b.solution.solution; + }); - // sort by work unit timestamp, with objective and solution values as tie-breakers - std::sort( - heuristic_solutions.begin(), - heuristic_solutions.end(), - [](const queued_integer_solution_t& a, const queued_integer_solution_t& b) { - if (a.work_timestamp != b.work_timestamp) { return a.work_timestamp < b.work_timestamp; } - if (a.objective != b.objective) { return a.objective < b.objective; } - return a.solution < b.solution; // edge-case - lexicographical comparison - }); + f_t deterministic_lower = deterministic_compute_lower_bound(); + f_t current_upper = upper_bound_.load(); + if (deterministic_current_horizon_ <= deterministic_horizon_step_ + 1e-9) { + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic solution replay: candidates=%zu lower=%.16e upper_before=%.16e\n", + replay_solutions.size(), + deterministic_lower, + current_upper); + for (size_t i = 0; i < replay_solutions.size(); ++i) { + const auto& replay = replay_solutions[i]; + const auto& sol = replay.solution; + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic replay solution[%zu]: wut=%.6f obj=%.16e origin=%s worker=%d seq=%d " + "depth=%d sol_hash=0x%x\n", + i, + sol.work_timestamp, + sol.objective, + cuopt::internals::mip_solution_origin_to_string(sol.origin), + sol.worker_id, + sol.sequence_id, + sol.depth, + detail::compute_hash(sol.solution)); + } + } - // Merge B&B events and heuristic solutions for unified timeline replay - size_t event_idx = 0; - size_t heuristic_idx = 0; + // Merge B&B events and all incumbent-producing solutions for unified timeline replay. + size_t event_idx = 0; + size_t solution_idx = 0; - while (event_idx < events.events.size() || heuristic_idx < heuristic_solutions.size()) { - bool process_event = false; - bool process_heuristic = false; + while (event_idx < events.events.size() || solution_idx < replay_solutions.size()) { + bool process_event = false; + bool process_solution = false; if (event_idx >= events.events.size()) { - process_heuristic = true; - } else if (heuristic_idx >= heuristic_solutions.size()) { + process_solution = true; + } else if (solution_idx >= replay_solutions.size()) { process_event = true; } else { - // Both have items - pick the one with smaller WUT if (events.events[event_idx].work_timestamp <= - heuristic_solutions[heuristic_idx].work_timestamp) { + replay_solutions[solution_idx].solution.work_timestamp) { process_event = true; } else { - process_heuristic = true; + process_solution = true; } } @@ -3368,43 +4075,72 @@ void branch_and_bound_t::deterministic_sort_replay_events( } } - if (process_heuristic) { - const auto& hsol = heuristic_solutions[heuristic_idx++]; - - CUOPT_LOG_TRACE( - "Deterministic sync: Heuristic solution received at WUT %f with objective %g, current " - "horizon %f", - hsol.work_timestamp, - hsol.objective, - deterministic_current_horizon_); + if (process_solution) { + const auto& replay = replay_solutions[solution_idx++]; + const auto& sol = replay.solution; + bool improved = false; - // Process heuristic solution at its correct work unit timestamp position - f_t new_upper = std::numeric_limits::infinity(); - - if (hsol.objective < upper_bound_) { - upper_bound_ = hsol.objective; - incumbent_.set_incumbent_solution(hsol.objective, hsol.solution); - new_upper = hsol.objective; + if (sol.objective < upper_bound_) { + const f_t previous_upper = upper_bound_; + upper_bound_ = sol.objective; + incumbent_.set_incumbent_solution(sol.objective, sol.solution); + current_upper = sol.objective; + improved = true; + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic B&B incumbent update: source=det_replay prev_upper=%.16e " + "new_upper=%.16e obj=%.16e hash=0x%x worker=%d seq=%d wut=%.6f horizon=%.6f\n", + previous_upper, + upper_bound_.load(), + sol.objective, + detail::compute_hash(sol.solution), + sol.worker_id, + sol.sequence_id, + sol.work_timestamp, + deterministic_current_horizon_); } - - if (new_upper < std::numeric_limits::infinity()) { - report_heuristic(new_upper); - - if (settings_.solution_callback != nullptr) { - std::vector original_x; - uncrush_primal_solution(original_problem_, original_lp_, hsol.solution, original_x); - settings_.solution_callback(original_x, hsol.objective); + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic replay: horizon=%.6f wut=%.6f obj=%.16e origin=%s accepted=%d " + "upper_now=%.16e worker=%d seq=%d sol_hash=0x%x\n", + deterministic_current_horizon_, + sol.work_timestamp, + sol.objective, + cuopt::internals::mip_solution_origin_to_string(sol.origin), + (int)improved, + current_upper, + sol.worker_id, + sol.sequence_id, + detail::compute_hash(sol.solution)); + + if (improved) { + CUOPT_DETERMINISM_LOG( + settings_.log, + "Deterministic replay PUBLISH: horizon=%.6f wut=%.6f obj=%g origin=%s worker=%d " + "upper_after=%.16e\n", + deterministic_current_horizon_, + sol.work_timestamp, + compute_user_objective(original_lp_, sol.objective), + cuopt::internals::mip_solution_origin_to_string(sol.origin), + sol.worker_id, + current_upper); + if (sol.origin == cuopt::internals::mip_solution_origin_t::BRANCH_AND_BOUND_NODE || + sol.origin == cuopt::internals::mip_solution_origin_t::BRANCH_AND_BOUND_DIVING) { + report(feasible_solution_symbol(replay.strategy), + sol.objective, + deterministic_lower, + sol.depth, + 0, + deterministic_current_horizon_); + } else { + report_heuristic(sol.objective, sol.work_timestamp); } + emit_solution_callback_from_crushed( + sol.solution, sol.objective, sol.origin, sol.work_timestamp); } } } - // Merge integer solutions from BFS workers and update global incumbent - deterministic_process_worker_solutions(*deterministic_workers_, - [](const deterministic_bfs_worker_pool_t&, int) { - return search_strategy_t::BEST_FIRST; - }); - // Merge and apply pseudo-cost updates from BFS workers deterministic_merge_pseudo_cost_updates(*deterministic_workers_); @@ -3460,52 +4196,44 @@ void branch_and_bound_t::deterministic_balance_worker_loads() constexpr bool force_rebalance_every_sync = false; - // Count work for each worker: current_node (if any) + plunge_stack + backlog - std::vector work_counts(num_workers); - size_t total_work = 0; - size_t max_work = 0; - size_t min_work = std::numeric_limits::max(); + std::vector backlog_counts(num_workers); + size_t total_backlog = 0; + size_t max_backlog = 0; + size_t min_backlog = std::numeric_limits::max(); for (size_t w = 0; w < num_workers; ++w) { - auto& worker = (*deterministic_workers_)[w]; - work_counts[w] = worker.queue_size(); - total_work += work_counts[w]; - max_work = std::max(max_work, work_counts[w]); - min_work = std::min(min_work, work_counts[w]); + auto& worker = (*deterministic_workers_)[w]; + backlog_counts[w] = worker.backlog.size(); + total_backlog += backlog_counts[w]; + max_backlog = std::max(max_backlog, backlog_counts[w]); + min_backlog = std::min(min_backlog, backlog_counts[w]); } - if (total_work == 0) return; + if (total_backlog == 0) return; bool needs_balance; if (force_rebalance_every_sync) { - needs_balance = (total_work > 1); + needs_balance = (total_backlog > 1); } else { - needs_balance = (min_work == 0 && max_work >= 2) || (min_work > 0 && max_work > 4 * min_work); + needs_balance = + (min_backlog == 0 && max_backlog >= 2) || (min_backlog > 0 && max_backlog > 4 * min_backlog); } if (!needs_balance) return; - std::vector*> all_nodes; + std::vector*> all_backlog_nodes; for (auto& worker : *deterministic_workers_) { for (auto* node : worker.backlog.data()) { - all_nodes.push_back(node); + all_backlog_nodes.push_back(node); } worker.backlog.clear(); } - if (all_nodes.empty()) return; + if (all_backlog_nodes.empty()) return; - auto deterministic_less = [](const mip_node_t* a, const mip_node_t* b) { - if (a->origin_worker_id != b->origin_worker_id) { - return a->origin_worker_id < b->origin_worker_id; - } - return a->creation_seq < b->creation_seq; - }; - std::sort(all_nodes.begin(), all_nodes.end(), deterministic_less); - - // Distribute nodes - for (size_t i = 0; i < all_nodes.size(); ++i) { + // Round-robin distribute into backlogs; priority queue handles ordering internally + for (size_t i = 0; i < all_backlog_nodes.size(); ++i) { size_t worker_idx = i % num_workers; - (*deterministic_workers_)[worker_idx].enqueue_node(all_nodes[i]); + (*deterministic_workers_)[worker_idx].backlog.push(all_backlog_nodes[i]); } } @@ -3625,16 +4353,18 @@ void branch_and_bound_t::deterministic_assign_diving_nodes() } template -void branch_and_bound_t::deterministic_collect_diving_solutions_and_update_pseudocosts() +void branch_and_bound_t::deterministic_collect_diving_solutions_and_update_pseudocosts( + std::vector::deterministic_replay_solution_t>& + replay_solutions) { if (!deterministic_diving_workers_) return; - // Collect integer solutions from diving workers and update global incumbent - deterministic_process_worker_solutions( + deterministic_collect_worker_solutions( *deterministic_diving_workers_, [](const deterministic_diving_worker_pool_t& pool, int worker_id) { return pool[worker_id].diving_type; - }); + }, + replay_solutions); // Merge pseudo-cost updates from diving workers deterministic_merge_pseudo_cost_updates(*deterministic_diving_workers_); @@ -3713,9 +4443,15 @@ void branch_and_bound_t::deterministic_dive( // Setup LP settings simplex_solver_settings_t lp_settings = settings_; lp_settings.set_log(false); - lp_settings.cut_off = worker.local_upper_bound + settings_.dual_tol; + if (original_lp_.objective_is_integral) { + lp_settings.cut_off = + std::ceil(worker.local_upper_bound - settings_.integer_tol) + settings_.dual_tol; + } else { + lp_settings.cut_off = worker.local_upper_bound + settings_.dual_tol; + } lp_settings.inside_mip = 2; lp_settings.time_limit = remaining_time; + lp_settings.work_limit = std::numeric_limits::infinity(); lp_settings.scale_columns = false; #ifndef DETERMINISM_DISABLE_BOUNDS_STRENGTHENING @@ -3723,7 +4459,6 @@ void branch_and_bound_t::deterministic_dive( lp_settings, worker.bounds_changed, worker.leaf_problem.lower, worker.leaf_problem.upper); if (settings_.deterministic) { - // TEMP APPROXIMATION; worker.work_context.record_work_sync_on_horizon(worker.node_presolver.last_nnz_processed / 1e8); } @@ -3777,17 +4512,17 @@ void branch_and_bound_t::deterministic_dive( lp_status = convert_lp_status_to_dual_status(second_status); } - ++nodes_this_dive; - ++worker.total_nodes_explored; worker.lp_iters_this_dive += node_iter; - - worker.clock = worker.work_context.global_work_units_elapsed; + worker.clock = pre_exploration_work_ + worker.work_context.global_work_units_elapsed; if (lp_status == dual::status_t::TIME_LIMIT || lp_status == dual::status_t::WORK_LIMIT || lp_status == dual::status_t::ITERATION_LIMIT) { break; } + ++nodes_this_dive; + ++worker.total_nodes_explored; + deterministic_diving_policy_t policy{*this, worker, stack, max_backtrack_depth}; update_tree_impl(node_ptr, dive_tree, &worker, lp_status, policy); } diff --git a/cpp/src/branch_and_bound/branch_and_bound.hpp b/cpp/src/branch_and_bound/branch_and_bound.hpp index 98674b7f9e..d4ab1af4b9 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.hpp +++ b/cpp/src/branch_and_bound/branch_and_bound.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -33,9 +34,11 @@ #include #include +#include #include #include #include +#include #include namespace cuopt::linear_programming::detail { @@ -103,10 +106,15 @@ class branch_and_bound_t { } // Set a solution based on the user problem during the course of the solve - void set_new_solution(const std::vector& solution); + void set_new_solution(const std::vector& solution, + cuopt::internals::mip_solution_origin_t origin = + cuopt::internals::mip_solution_origin_t::UNKNOWN); // This queues the solution to be processed at the correct work unit timestamp - void queue_external_solution_deterministic(const std::vector& solution, double work_unit_ts); + void queue_external_solution_deterministic(const std::vector& solution, + f_t user_objective, + double work_unit_ts, + cuopt::internals::mip_solution_origin_t origin); void set_user_bound_callback(std::function callback) { @@ -122,6 +130,7 @@ class branch_and_bound_t { std::vector& repaired_solution) const; f_t get_lower_bound(); + f_t get_upper_bound() const { return upper_bound_; } bool enable_concurrent_lp_root_solve() const { return enable_concurrent_lp_root_solve_; } std::atomic* get_root_concurrent_halt() { return &root_concurrent_halt_; } void set_root_concurrent_halt(int value) { root_concurrent_halt_ = value; } @@ -145,6 +154,12 @@ class branch_and_bound_t { // Get producer sync for external heuristics (e.g., CPUFJ) to register producer_sync_t& get_producer_sync() { return producer_sync_; } + void wait_for_exploration_start() + { + std::unique_lock lock(exploration_started_mutex_); + exploration_started_cv_.wait(lock, [this] { return exploration_started_.load(); }); + } + private: const user_problem_t& original_problem_; const simplex_solver_settings_t settings_; @@ -153,6 +168,10 @@ class branch_and_bound_t { std::atomic signal_extend_cliques_{false}; work_limit_context_t work_unit_context_{"B&B"}; + double pre_exploration_work_{0.0}; + std::atomic exploration_started_{false}; + std::mutex exploration_started_mutex_; + std::condition_variable exploration_started_cv_; // Initial guess. std::vector guess_; @@ -190,7 +209,13 @@ class branch_and_bound_t { // Mutex for repair omp_mutex_t mutex_repair_; - std::vector> repair_queue_; + struct queued_repair_solution_t { + std::vector solution; + cuopt::internals::mip_solution_origin_t origin{ + cuopt::internals::mip_solution_origin_t::UNKNOWN}; + double work_timestamp{-1.0}; + }; + std::vector repair_queue_; // Variables for the root node in the search tree. std::vector root_vstatus_; @@ -236,13 +261,21 @@ class branch_and_bound_t { omp_atomic_t lower_bound_ceiling_; std::function user_bound_callback_; - void report_heuristic(f_t obj); + void report_heuristic(f_t obj, double work_time = -1.0); void report(char symbol, f_t obj, f_t lower_bound, i_t node_depth, i_t node_int_infeas, double work_time = -1); + void emit_solution_callback(std::vector& original_x, + f_t objective, + cuopt::internals::mip_solution_origin_t origin, + double work_timestamp); + void emit_solution_callback_from_crushed(const std::vector& crushed_solution, + f_t objective, + cuopt::internals::mip_solution_origin_t origin, + double work_timestamp); // Set the solution when found at the root node void set_solution_at_root(mip_solution_t& solution, @@ -315,7 +348,14 @@ class branch_and_bound_t { void run_deterministic_coordinator(const csr_matrix_t& Arow); // Gather all events generated, sort by WU timestamp, apply - void deterministic_sort_replay_events(const bb_event_batch_t& events); + struct deterministic_replay_solution_t { + queued_integer_solution_t solution; + search_strategy_t strategy{search_strategy_t::BEST_FIRST}; + }; + + void deterministic_sort_replay_events( + const bb_event_batch_t& events, + std::vector& replay_solutions); // Prune nodes held by workers based on new incumbent void deterministic_prune_worker_nodes_vs_incumbent(); @@ -348,10 +388,14 @@ class branch_and_bound_t { void deterministic_assign_diving_nodes(); // Collect and merge diving solutions at sync - void deterministic_collect_diving_solutions_and_update_pseudocosts(); + void deterministic_collect_diving_solutions_and_update_pseudocosts( + std::vector& replay_solutions); template - void deterministic_process_worker_solutions(PoolT& pool, WorkerTypeGetter get_worker_type); + void deterministic_collect_worker_solutions( + PoolT& pool, + WorkerTypeGetter get_worker_type, + std::vector& replay_solutions); template void deterministic_merge_pseudo_cost_updates(PoolT& pool); @@ -382,10 +426,23 @@ class branch_and_bound_t { double max_producer_wait_time_{0.0}; i_t producer_wait_count_{0}; - // Determinism heuristic solution queue - solutions received from GPU heuristics - // Stored with work unit timestamp for deterministic ordering + struct queued_external_solution_t { + std::vector solution; + f_t user_objective{std::numeric_limits::infinity()}; + double work_timestamp{0.0}; + cuopt::internals::mip_solution_origin_t origin{ + cuopt::internals::mip_solution_origin_t::UNKNOWN}; + }; + + bool retire_queued_solution(const queued_external_solution_t& queued_solution, + f_t& out_obj, + std::vector& out_crushed); + + // Deterministic pending external solution queue. + // External solutions stay raw until their retirement horizon, where they are + // crushed, checked, and repaired immediately if needed. omp_mutex_t mutex_heuristic_queue_; - std::vector> heuristic_solution_queue_; + std::vector heuristic_solution_queue_; // ============================================================================ // Determinism Diving state diff --git a/cpp/src/branch_and_bound/deterministic_workers.hpp b/cpp/src/branch_and_bound/deterministic_workers.hpp index 7a074051c6..f6f9a1bd51 100644 --- a/cpp/src/branch_and_bound/deterministic_workers.hpp +++ b/cpp/src/branch_and_bound/deterministic_workers.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -44,6 +45,8 @@ struct queued_integer_solution_t { int worker_id{-1}; int sequence_id{0}; double work_timestamp{0.0}; + cuopt::internals::mip_solution_origin_t origin{ + cuopt::internals::mip_solution_origin_t::BRANCH_AND_BOUND_NODE}; bool operator<(const queued_integer_solution_t& other) const { @@ -59,6 +62,7 @@ struct deterministic_snapshot_t { pseudo_cost_snapshot_t pc_snapshot; std::vector incumbent; i_t total_lp_iters; + i_t nodes_explored; }; template @@ -75,6 +79,7 @@ class deterministic_worker_base_t : public branch_and_bound_worker_t { // Diving-specific snapshots (ignored by BFS workers) std::vector incumbent_snapshot; i_t total_lp_iters_snapshot{0}; + i_t nodes_explored_snapshot{0}; std::vector> integer_solutions; int next_solution_seq{0}; @@ -101,6 +106,7 @@ class deterministic_worker_base_t : public branch_and_bound_worker_t { pc_snapshot = snap.pc_snapshot; incumbent_snapshot = snap.incumbent; total_lp_iters_snapshot = snap.total_lp_iters; + nodes_explored_snapshot = snap.nodes_explored; } bool has_work() const { return static_cast(this)->has_work_impl(); } @@ -158,11 +164,6 @@ class deterministic_bfs_worker_t mip_node_t* up_child, rounding_direction_t preferred_direction) { - if (!plunge_stack.empty()) { - backlog.push(plunge_stack.back()); - plunge_stack.pop_back(); - } - down_child->origin_worker_id = this->worker_id; down_child->creation_seq = next_creation_seq++; up_child->origin_worker_id = this->worker_id; @@ -170,11 +171,11 @@ class deterministic_bfs_worker_t mip_node_t* first_child; if (preferred_direction == rounding_direction_t::UP) { - plunge_stack.push_front(down_child); + backlog.push(down_child); plunge_stack.push_front(up_child); first_child = up_child; } else { - plunge_stack.push_front(up_child); + backlog.push(up_child); plunge_stack.push_front(down_child); first_child = down_child; } @@ -288,6 +289,7 @@ class deterministic_diving_worker_t // Diving statistics i_t total_nodes_explored{0}; + i_t nodes_explored_last_sync{0}; i_t total_dives{0}; i_t lp_iters_this_dive{0}; @@ -339,7 +341,13 @@ class deterministic_diving_worker_t void queue_integer_solution(f_t objective, const std::vector& solution, i_t depth) { this->integer_solutions.push_back( - {objective, solution, depth, this->worker_id, this->next_solution_seq++}); + {objective, + solution, + depth, + this->worker_id, + this->next_solution_seq++, + this->clock, + cuopt::internals::mip_solution_origin_t::BRANCH_AND_BOUND_DIVING}); ++this->total_integer_solutions; } diff --git a/cpp/src/branch_and_bound/pseudo_costs.cpp b/cpp/src/branch_and_bound/pseudo_costs.cpp index ee7e2f7803..7be411ac7b 100644 --- a/cpp/src/branch_and_bound/pseudo_costs.cpp +++ b/cpp/src/branch_and_bound/pseudo_costs.cpp @@ -34,7 +34,8 @@ void strong_branch_helper(i_t start, const std::vector& root_soln, const std::vector& root_vstatus, const std::vector& edge_norms, - pseudo_costs_t& pc) + pseudo_costs_t& pc, + cuopt::work_limit_context_t* work_unit_context) { raft::common::nvtx::range scope("BB::strong_branch_helper"); lp_problem_t child_problem = original_lp; @@ -74,7 +75,8 @@ void strong_branch_helper(i_t start, vstatus, solution, iter, - child_edge_norms); + child_edge_norms, + work_unit_context); f_t obj = std::numeric_limits::quiet_NaN(); if (status == dual::status_t::DUAL_UNBOUNDED) { @@ -216,8 +218,276 @@ f_t trial_branching(const lp_problem_t& original_lp, } } +template +f_t trial_branching_generic(const lp_problem_t& original_lp, + const simplex_solver_settings_t& settings, + const std::vector& var_types, + const std::vector& vstatus, + const std::vector& edge_norms, + const basis_update_mpf_t& basis_factors, + const std::vector& basic_list, + const std::vector& nonbasic_list, + i_t branch_var, + f_t branch_var_lower, + f_t branch_var_upper, + f_t upper_bound, + i_t bnb_lp_iter_per_node, + f_t start_time, + i_t upper_max_lp_iter, + i_t lower_max_lp_iter, + omp_atomic_t& total_lp_iter) +{ + return trial_branching(original_lp, + settings, + var_types, + vstatus, + edge_norms, + basis_factors, + basic_list, + nonbasic_list, + branch_var, + branch_var_lower, + branch_var_upper, + upper_bound, + bnb_lp_iter_per_node, + start_time, + upper_max_lp_iter, + lower_max_lp_iter, + total_lp_iter); +} + +template +f_t trial_branching_generic(const lp_problem_t& original_lp, + const simplex_solver_settings_t& settings, + const std::vector& var_types, + const std::vector& vstatus, + const std::vector& edge_norms, + const basis_update_mpf_t& basis_factors, + const std::vector& basic_list, + const std::vector& nonbasic_list, + i_t branch_var, + f_t branch_var_lower, + f_t branch_var_upper, + f_t upper_bound, + i_t bnb_lp_iter_per_node, + f_t start_time, + i_t upper_max_lp_iter, + i_t lower_max_lp_iter, + int64_t& total_lp_iter) +{ + omp_atomic_t atomic_iter{0}; + f_t result = trial_branching(original_lp, + settings, + var_types, + vstatus, + edge_norms, + basis_factors, + basic_list, + nonbasic_list, + branch_var, + branch_var_lower, + branch_var_upper, + upper_bound, + bnb_lp_iter_per_node, + start_time, + upper_max_lp_iter, + lower_max_lp_iter, + atomic_iter); + total_lp_iter += atomic_iter.load(); + return result; +} + } // namespace +template +i_t reliable_variable_selection_core(mip_node_t* node_ptr, + const std::vector& fractional, + const std::vector& solution, + const simplex_solver_settings_t& settings, + const std::vector& var_types, + const lp_problem_t& leaf_problem, + const std::vector& edge_norms, + const basis_update_mpf_t& basis_factors, + const std::vector& basic_list, + const std::vector& nonbasic_list, + SumT* sum_down, + SumT* sum_up, + CountT* num_down, + CountT* num_up, + i_t n_vars, + SBIterT& strong_branching_lp_iter, + f_t upper_bound, + int64_t bnb_lp_iters, + int64_t bnb_nodes_explored, + f_t start_time, + const reliability_branching_settings_t& rb_settings, + int num_tasks, + omp_mutex_t* var_mutex_down, + omp_mutex_t* var_mutex_up, + pcgenerator_t* rng) +{ + constexpr f_t eps = 1e-6; + i_t branch_var = fractional[0]; + f_t max_score = -1; + + auto avgs = compute_pseudo_cost_averages(sum_down, sum_up, num_down, num_up, (size_t)n_vars); + f_t pseudo_cost_down_avg = avgs.down_avg; + f_t pseudo_cost_up_avg = avgs.up_avg; + + const i_t bnb_lp_iter_per_node = + bnb_nodes_explored > 0 ? (i_t)(bnb_lp_iters / bnb_nodes_explored) : 0; + + i_t reliable_threshold = settings.reliability_branching; + if (reliable_threshold < 0) { + const int64_t alpha = (int64_t)(rb_settings.bnb_lp_factor * bnb_lp_iters); + const int64_t max_reliability_iter = alpha + rb_settings.bnb_lp_offset; + + f_t iter_fraction = + (max_reliability_iter - strong_branching_lp_iter) / (strong_branching_lp_iter + 1.0); + iter_fraction = std::min(1.0, iter_fraction); + iter_fraction = std::max((alpha - strong_branching_lp_iter) / (strong_branching_lp_iter + 1.0), + iter_fraction); + reliable_threshold = (int)((1 - iter_fraction) * rb_settings.min_reliable_threshold + + iter_fraction * rb_settings.max_reliable_threshold); + reliable_threshold = strong_branching_lp_iter < max_reliability_iter ? reliable_threshold : 0; + } + + std::vector unreliable_list; + + for (i_t j : fractional) { + if (num_down[j] < reliable_threshold || num_up[j] < reliable_threshold) { + unreliable_list.push_back(j); + continue; + } + f_t pc_down = num_down[j] > 0 ? sum_down[j] / num_down[j] : pseudo_cost_down_avg; + f_t pc_up = num_up[j] > 0 ? sum_up[j] / num_up[j] : pseudo_cost_up_avg; + f_t f_down = solution[j] - std::floor(solution[j]); + f_t f_up = std::ceil(solution[j]) - solution[j]; + f_t score = std::max(f_down * pc_down, eps) * std::max(f_up * pc_up, eps); + if (score > max_score) { + max_score = score; + branch_var = j; + } + } + + if (unreliable_list.empty()) { return branch_var; } + + const i_t max_num_candidates = rb_settings.max_num_candidates; + const i_t num_candidates = std::min(unreliable_list.size(), max_num_candidates); + + settings.log.debug( + "Reliability branching: node=%d depth=%d fractional=%zu unreliable=%zu candidates=%d " + "threshold=%d sb_iters=%lld bnb_iters=%lld explored=%lld tasks=%d\n", + node_ptr->node_id, + node_ptr->depth, + fractional.size(), + unreliable_list.size(), + num_candidates, + reliable_threshold, + (long long)strong_branching_lp_iter, + (long long)bnb_lp_iters, + (long long)bnb_nodes_explored, + num_tasks); + + if (rng != nullptr && unreliable_list.size() > (size_t)max_num_candidates) { + rng->shuffle(unreliable_list); + } + + if (toc(start_time) > settings.time_limit) { return branch_var; } + + omp_mutex_t score_mutex; + const int task_priority = rb_settings.task_priority; + +#pragma omp taskloop if (num_tasks > 1) priority(task_priority) num_tasks(num_tasks) \ + shared(score_mutex, strong_branching_lp_iter) + for (i_t i = 0; i < num_candidates; ++i) { + const i_t j = unreliable_list[i]; + + if (toc(start_time) > settings.time_limit) { continue; } + + if (var_mutex_down) { var_mutex_down[j].lock(); } + if (num_down[j] < reliable_threshold) { + f_t obj = trial_branching_generic(leaf_problem, + settings, + var_types, + node_ptr->vstatus, + edge_norms, + basis_factors, + basic_list, + nonbasic_list, + j, + leaf_problem.lower[j], + std::floor(solution[j]), + upper_bound, + bnb_lp_iter_per_node, + start_time, + rb_settings.upper_max_lp_iter, + rb_settings.lower_max_lp_iter, + strong_branching_lp_iter); + if (!std::isnan(obj)) { + f_t change_in_obj = std::max(obj - node_ptr->lower_bound, eps); + f_t change_in_x = solution[j] - std::floor(solution[j]); + sum_down[j] += change_in_obj / change_in_x; + num_down[j]++; + } + } + if (var_mutex_down) { var_mutex_down[j].unlock(); } + + if (toc(start_time) > settings.time_limit) { continue; } + + if (var_mutex_up) { var_mutex_up[j].lock(); } + if (num_up[j] < reliable_threshold) { + f_t obj = trial_branching_generic(leaf_problem, + settings, + var_types, + node_ptr->vstatus, + edge_norms, + basis_factors, + basic_list, + nonbasic_list, + j, + std::ceil(solution[j]), + leaf_problem.upper[j], + upper_bound, + bnb_lp_iter_per_node, + start_time, + rb_settings.upper_max_lp_iter, + rb_settings.lower_max_lp_iter, + strong_branching_lp_iter); + if (!std::isnan(obj)) { + f_t change_in_obj = std::max(obj - node_ptr->lower_bound, eps); + f_t change_in_x = std::ceil(solution[j]) - solution[j]; + sum_up[j] += change_in_obj / change_in_x; + num_up[j]++; + } + } + if (var_mutex_up) { var_mutex_up[j].unlock(); } + + if (toc(start_time) > settings.time_limit) { continue; } + + f_t pc_down = num_down[j] > 0 ? sum_down[j] / num_down[j] : pseudo_cost_down_avg; + f_t pc_up = num_up[j] > 0 ? sum_up[j] / num_up[j] : pseudo_cost_up_avg; + f_t f_down = solution[j] - std::floor(solution[j]); + f_t f_up = std::ceil(solution[j]) - solution[j]; + f_t score = std::max(f_down * pc_down, eps) * std::max(f_up * pc_up, eps); + + score_mutex.lock(); + if (score > max_score) { + max_score = score; + branch_var = j; + } + score_mutex.unlock(); + } + + settings.log.debug("Reliability branching result: node=%d branch_var=%d value=%e score=%e\n", + node_ptr->node_id, + branch_var, + solution[branch_var], + max_score); + + return branch_var; +} + template static cuopt::mps_parser::mps_data_model_t simplex_problem_to_mps_data_model( const dual_simplex::user_problem_t& user_problem) @@ -307,7 +577,8 @@ void strong_branching(const user_problem_t& original_problem, f_t root_obj, const std::vector& root_vstatus, const std::vector& edge_norms, - pseudo_costs_t& pc) + pseudo_costs_t& pc, + cuopt::work_limit_context_t* work_unit_context) { pc.resize(original_lp.num_cols); pc.strong_branch_down.assign(fractional.size(), 0); @@ -400,16 +671,25 @@ void strong_branching(const user_problem_t& original_problem, fractional.size()); f_t strong_branching_start_time = tic(); + const bool use_work_accounting = work_unit_context && work_unit_context->deterministic; + // More tasks than threads in order to allow for dynamic load balancing through OpenMP. + // work context accounting needs to be one on a task basis to avoid + // nondeterminism, because openmp is free to schedule threads as it likes. + const i_t n_tasks = std::min(4 * settings.num_threads, fractional.size()); + std::vector task_work_contexts; + if (use_work_accounting) { + for (i_t k = 0; k < n_tasks; ++k) { + task_work_contexts.emplace_back("sb_task_" + std::to_string(k)); + task_work_contexts.back().deterministic = true; + } + } + #pragma omp parallel num_threads(settings.num_threads) { - i_t n = std::min(4 * settings.num_threads, fractional.size()); - - // Here we are creating more tasks than the number of threads - // such that they can be scheduled dynamically to the threads. #pragma omp for schedule(dynamic, 1) - for (i_t k = 0; k < n; k++) { - i_t start = std::floor(k * fractional.size() / n); - i_t end = std::floor((k + 1) * fractional.size() / n); + for (i_t k = 0; k < n_tasks; k++) { + i_t start = std::floor(k * fractional.size() / n_tasks); + i_t end = std::floor((k + 1) * fractional.size() / n_tasks); constexpr bool verbose = false; if (verbose) { @@ -421,6 +701,9 @@ void strong_branching(const user_problem_t& original_problem, end - start); } + cuopt::work_limit_context_t* task_ctx = + use_work_accounting ? &task_work_contexts[k] : nullptr; + strong_branch_helper(start, end, start_time, @@ -432,9 +715,21 @@ void strong_branching(const user_problem_t& original_problem, root_soln, root_vstatus, edge_norms, - pc); + pc, + task_ctx); } } + + // record pre-exploration work by taking the max work performed by any task + // important to aggregate by task and not thread, as openmp uses dynamic scheduling here + if (use_work_accounting) { + double max_work = 0.0; + for (auto& ctx : task_work_contexts) { + max_work = std::max(max_work, ctx.current_work()); + } + work_unit_context->record_work_sync_on_horizon(max_work); + } + settings.log.printf("Strong branching completed in %.2fs\n", toc(strong_branching_start_time)); } @@ -540,181 +835,31 @@ i_t pseudo_costs_t::reliable_variable_selection( int max_num_tasks, logger_t& log) { - constexpr f_t eps = 1e-6; - f_t start_time = bnb_stats.start_time; - i_t branch_var = fractional[0]; - f_t max_score = -1; - i_t num_initialized_down; - i_t num_initialized_up; - f_t pseudo_cost_down_avg; - f_t pseudo_cost_up_avg; - - initialized(num_initialized_down, num_initialized_up, pseudo_cost_down_avg, pseudo_cost_up_avg); - - log.printf("PC: num initialized down %d up %d avg down %e up %e\n", - num_initialized_down, - num_initialized_up, - pseudo_cost_down_avg, - pseudo_cost_up_avg); - - const int64_t branch_and_bound_lp_iters = bnb_stats.total_lp_iters; - const int64_t branch_and_bound_explored = bnb_stats.nodes_explored; - const i_t branch_and_bound_lp_iter_per_node = - branch_and_bound_lp_iters / bnb_stats.nodes_explored; - - i_t reliable_threshold = settings.reliability_branching; - if (reliable_threshold < 0) { - const i_t max_threshold = reliability_branching_settings.max_reliable_threshold; - const i_t min_threshold = reliability_branching_settings.min_reliable_threshold; - const f_t iter_factor = reliability_branching_settings.bnb_lp_factor; - const i_t iter_offset = reliability_branching_settings.bnb_lp_offset; - const int64_t alpha = iter_factor * branch_and_bound_lp_iters; - const int64_t max_reliability_iter = alpha + reliability_branching_settings.bnb_lp_offset; - - f_t iter_fraction = - (max_reliability_iter - strong_branching_lp_iter) / (strong_branching_lp_iter + 1.0); - iter_fraction = std::min(1.0, iter_fraction); - iter_fraction = std::max((alpha - strong_branching_lp_iter) / (strong_branching_lp_iter + 1.0), - iter_fraction); - reliable_threshold = (1 - iter_fraction) * min_threshold + iter_fraction * max_threshold; - reliable_threshold = strong_branching_lp_iter < max_reliability_iter ? reliable_threshold : 0; - } - - std::vector unreliable_list; - omp_mutex_t score_mutex; - - for (i_t j : fractional) { - if (pseudo_cost_num_down[j] < reliable_threshold || - pseudo_cost_num_up[j] < reliable_threshold) { - unreliable_list.push_back(j); - continue; - } - - f_t score = calculate_pseudocost_score(j, solution, pseudo_cost_up_avg, pseudo_cost_down_avg); - - if (score > max_score) { - max_score = score; - branch_var = j; - } - } - - if (unreliable_list.empty()) { - log.printf( - "pc branching on %d. Value %e. Score %e\n", branch_var, solution[branch_var], max_score); - - return branch_var; - } - - const int num_tasks = std::max(max_num_tasks, 1); - const int task_priority = reliability_branching_settings.task_priority; - const i_t max_num_candidates = reliability_branching_settings.max_num_candidates; - const i_t num_candidates = std::min(unreliable_list.size(), max_num_candidates); - - assert(task_priority > 0); - assert(max_num_candidates > 0); - assert(num_candidates > 0); - assert(num_tasks > 0); - - log.printf( - "RB iters = %d, B&B iters = %d, unreliable = %d, num_tasks = %d, reliable_threshold = %d\n", - strong_branching_lp_iter.load(), - branch_and_bound_lp_iters, - unreliable_list.size(), - num_tasks, - reliable_threshold); - - // Shuffle the unreliable list so every variable has the same chance to be selected. - if (unreliable_list.size() > max_num_candidates) { worker->rng.shuffle(unreliable_list); } - - if (toc(start_time) > settings.time_limit) { - log.printf("Time limit reached"); - return branch_var; - } - -#pragma omp taskloop if (num_tasks > 1) priority(task_priority) num_tasks(num_tasks) \ - shared(score_mutex) - for (i_t i = 0; i < num_candidates; ++i) { - const i_t j = unreliable_list[i]; - - if (toc(start_time) > settings.time_limit) { continue; } - - pseudo_cost_mutex_down[j].lock(); - if (pseudo_cost_num_down[j] < reliable_threshold) { - // Do trial branching on the down branch - f_t obj = trial_branching(worker->leaf_problem, - settings, - var_types, - node_ptr->vstatus, - worker->leaf_edge_norms, - worker->basis_factors, - worker->basic_list, - worker->nonbasic_list, - j, - worker->leaf_problem.lower[j], - std::floor(solution[j]), - upper_bound, - branch_and_bound_lp_iter_per_node, - start_time, - reliability_branching_settings.upper_max_lp_iter, - reliability_branching_settings.lower_max_lp_iter, - strong_branching_lp_iter); - - if (!std::isnan(obj)) { - f_t change_in_obj = std::max(obj - node_ptr->lower_bound, eps); - f_t change_in_x = solution[j] - std::floor(solution[j]); - pseudo_cost_sum_down[j] += change_in_obj / change_in_x; - pseudo_cost_num_down[j]++; - } - } - pseudo_cost_mutex_down[j].unlock(); - - if (toc(start_time) > settings.time_limit) { continue; } - - pseudo_cost_mutex_up[j].lock(); - if (pseudo_cost_num_up[j] < reliable_threshold) { - f_t obj = trial_branching(worker->leaf_problem, - settings, - var_types, - node_ptr->vstatus, - worker->leaf_edge_norms, - worker->basis_factors, - worker->basic_list, - worker->nonbasic_list, - j, - std::ceil(solution[j]), - worker->leaf_problem.upper[j], - upper_bound, - branch_and_bound_lp_iter_per_node, - start_time, - reliability_branching_settings.upper_max_lp_iter, - reliability_branching_settings.lower_max_lp_iter, - strong_branching_lp_iter); - - if (!std::isnan(obj)) { - f_t change_in_obj = std::max(obj - node_ptr->lower_bound, eps); - f_t change_in_x = std::ceil(solution[j]) - solution[j]; - pseudo_cost_sum_up[j] += change_in_obj / change_in_x; - pseudo_cost_num_up[j]++; - } - } - pseudo_cost_mutex_up[j].unlock(); - - if (toc(start_time) > settings.time_limit) { continue; } - - f_t score = calculate_pseudocost_score(j, solution, pseudo_cost_up_avg, pseudo_cost_down_avg); - - score_mutex.lock(); - if (score > max_score) { - max_score = score; - branch_var = j; - } - score_mutex.unlock(); - } - - log.printf( - "pc branching on %d. Value %e. Score %e\n", branch_var, solution[branch_var], max_score); - - return branch_var; + return reliable_variable_selection_core(node_ptr, + fractional, + solution, + settings, + var_types, + worker->leaf_problem, + worker->leaf_edge_norms, + worker->basis_factors, + worker->basic_list, + worker->nonbasic_list, + pseudo_cost_sum_down.data(), + pseudo_cost_sum_up.data(), + pseudo_cost_num_down.data(), + pseudo_cost_num_up.data(), + (i_t)pseudo_cost_sum_down.size(), + strong_branching_lp_iter, + upper_bound, + bnb_stats.total_lp_iters, + bnb_stats.nodes_explored, + bnb_stats.start_time, + reliability_branching_settings, + std::max(max_num_tasks, 1), + pseudo_cost_mutex_down.data(), + pseudo_cost_mutex_up.data(), + &worker->rng); } template @@ -776,6 +921,66 @@ void pseudo_costs_t::update_pseudo_costs_from_strong_branching( template class pseudo_costs_t; +// Opportunistic: omp_atomic_t arrays, omp_atomic_t sb_iter +template int reliable_variable_selection_core, + omp_atomic_t, + omp_atomic_t>( + mip_node_t*, + const std::vector&, + const std::vector&, + const simplex_solver_settings_t&, + const std::vector&, + const lp_problem_t&, + const std::vector&, + const basis_update_mpf_t&, + const std::vector&, + const std::vector&, + omp_atomic_t*, + omp_atomic_t*, + omp_atomic_t*, + omp_atomic_t*, + int, + omp_atomic_t&, + double, + int64_t, + int64_t, + double, + const reliability_branching_settings_t&, + int, + omp_mutex_t*, + omp_mutex_t*, + pcgenerator_t*); + +// Deterministic: plain arrays, plain int64_t sb_iter +template int reliable_variable_selection_core( + mip_node_t*, + const std::vector&, + const std::vector&, + const simplex_solver_settings_t&, + const std::vector&, + const lp_problem_t&, + const std::vector&, + const basis_update_mpf_t&, + const std::vector&, + const std::vector&, + double*, + double*, + int*, + int*, + int, + int64_t&, + double, + int64_t, + int64_t, + double, + const reliability_branching_settings_t&, + int, + omp_mutex_t*, + omp_mutex_t*, + pcgenerator_t*); + template void strong_branching(const user_problem_t& original_problem, const lp_problem_t& original_lp, const simplex_solver_settings_t& settings, @@ -786,7 +991,8 @@ template void strong_branching(const user_problem_t& o double root_obj, const std::vector& root_vstatus, const std::vector& edge_norms, - pseudo_costs_t& pc); + pseudo_costs_t& pc, + cuopt::work_limit_context_t* work_unit_context); #endif diff --git a/cpp/src/branch_and_bound/pseudo_costs.hpp b/cpp/src/branch_and_bound/pseudo_costs.hpp index 6b6c6917b6..11c377febd 100644 --- a/cpp/src/branch_and_bound/pseudo_costs.hpp +++ b/cpp/src/branch_and_bound/pseudo_costs.hpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -367,6 +368,7 @@ class pseudo_cost_snapshot_t { std::vector sum_up_; std::vector num_down_; std::vector num_up_; + int64_t strong_branching_lp_iter_{0}; private: std::vector> updates_; @@ -431,8 +433,10 @@ class pseudo_costs_t { nd[j] = pseudo_cost_num_down[j]; nu[j] = pseudo_cost_num_up[j]; } - return pseudo_cost_snapshot_t( - std::move(sd), std::move(su), std::move(nd), std::move(nu)); + auto snap = + pseudo_cost_snapshot_t(std::move(sd), std::move(su), std::move(nd), std::move(nu)); + snap.strong_branching_lp_iter_ = strong_branching_lp_iter.load(); + return snap; } void merge_updates(const std::vector>& updates) @@ -516,6 +520,37 @@ class pseudo_costs_t { omp_atomic_t strong_branching_lp_iter = 0; }; +// Core reliability branching loop usable by both opportunistic and deterministic paths. +// When num_tasks == 1, runs serially with no locking (deterministic). +// When num_tasks > 1 with mutexes/rng, uses OMP taskloop (opportunistic). +// SumT/CountT can be f_t/i_t (deterministic snapshot) or omp_atomic_t/omp_atomic_t. +template +i_t reliable_variable_selection_core(mip_node_t* node_ptr, + const std::vector& fractional, + const std::vector& solution, + const simplex_solver_settings_t& settings, + const std::vector& var_types, + const lp_problem_t& leaf_problem, + const std::vector& edge_norms, + const basis_update_mpf_t& basis_factors, + const std::vector& basic_list, + const std::vector& nonbasic_list, + SumT* sum_down, + SumT* sum_up, + CountT* num_down, + CountT* num_up, + i_t n_vars, + SBIterT& strong_branching_lp_iter, + f_t upper_bound, + int64_t bnb_lp_iters, + int64_t bnb_nodes_explored, + f_t start_time, + const reliability_branching_settings_t& rb_settings, + int num_tasks, + omp_mutex_t* var_mutex_down, + omp_mutex_t* var_mutex_up, + pcgenerator_t* rng); + template void strong_branching(const user_problem_t& original_problem, const lp_problem_t& original_lp, @@ -527,6 +562,7 @@ void strong_branching(const user_problem_t& original_problem, f_t root_obj, const std::vector& root_vstatus, const std::vector& edge_norms, - pseudo_costs_t& pc); + pseudo_costs_t& pc, + cuopt::work_limit_context_t* work_unit_context = nullptr); } // namespace cuopt::linear_programming::dual_simplex diff --git a/cpp/src/dual_simplex/basis_updates.cpp b/cpp/src/dual_simplex/basis_updates.cpp index 9c56ada50e..28a3845378 100644 --- a/cpp/src/dual_simplex/basis_updates.cpp +++ b/cpp/src/dual_simplex/basis_updates.cpp @@ -2202,7 +2202,7 @@ i_t basis_update_mpf_t::update(const sparse_vector_t& utilde // Ensure the workspace is sorted. Otherwise, the sparse dot will be incorrect. std::sort(xi_workspace_.begin() + m, xi_workspace_.begin() + m + nz, std::less()); - work_estimate_ += (m + nz) * std::log2(m + nz); + if ((m + nz) > 1) { work_estimate_ += (m + nz) * std::log2((f_t)(m + nz)); } // Gather the workspace into a column of S i_t S_start; @@ -2214,7 +2214,7 @@ i_t basis_update_mpf_t::update(const sparse_vector_t& utilde // Gather etilde into a column of S etilde.sort(); // Needs to be sorted for the sparse dot. TODO(CMM): Is etilde sorted on input? - work_estimate_ += etilde.i.size() * std::log2(etilde.i.size()); + if (etilde.i.size() > 1) { work_estimate_ += etilde.i.size() * std::log2((f_t)etilde.i.size()); } S_.append_column(etilde); work_estimate_ += 4 * etilde.i.size(); diff --git a/cpp/src/dual_simplex/bound_flipping_ratio_test.cpp b/cpp/src/dual_simplex/bound_flipping_ratio_test.cpp index e30b067398..d9abc26fe1 100644 --- a/cpp/src/dual_simplex/bound_flipping_ratio_test.cpp +++ b/cpp/src/dual_simplex/bound_flipping_ratio_test.cpp @@ -235,7 +235,7 @@ void bound_flipping_ratio_test_t::heap_passes(const std::vector& // Remove minimum ratio from the heap and rebalance i_t heap_index = bare_idx.front(); std::pop_heap(bare_idx.begin(), bare_idx.end(), compare); - work_estimate_ += 2 * std::log2(bare_idx.size()); + if (bare_idx.size() > 1) { work_estimate_ += 2 * std::log2((f_t)bare_idx.size()); } bare_idx.pop_back(); nonbasic_entering = current_indicies[heap_index]; diff --git a/cpp/src/dual_simplex/bound_flipping_ratio_test.hpp b/cpp/src/dual_simplex/bound_flipping_ratio_test.hpp index 244ff334df..4b62c66771 100644 --- a/cpp/src/dual_simplex/bound_flipping_ratio_test.hpp +++ b/cpp/src/dual_simplex/bound_flipping_ratio_test.hpp @@ -100,7 +100,7 @@ class bound_flipping_ratio_test_t { i_t n_; i_t m_; - f_t work_estimate_; + f_t work_estimate_{0.0}; }; } // namespace cuopt::linear_programming::dual_simplex diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index 43429ba2de..50f13f0e04 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -3544,7 +3544,9 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase, phase2_work_estimate += ft.work_estimate(); ft.clear_work_estimate(); - work_unit_context->record_work_sync_on_horizon(phase2_work_estimate / 1e8); + if (work_unit_context) { + work_unit_context->record_work_sync_on_horizon(phase2_work_estimate / 1e8); + } phase2_work_estimate = 0.0; last_feature_log_iter = iter; diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index eadd93040c..57514a7488 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -7,6 +7,7 @@ #pragma once +#include #include #include @@ -109,7 +110,7 @@ struct simplex_solver_settings_t { reliability_branching(-1), inside_mip(0), sub_mip(0), - solution_callback(nullptr), + new_incumbent_callback(nullptr), heuristic_preemption_callback(nullptr), dual_simplex_objective_callback(nullptr), concurrent_halt(nullptr) @@ -189,6 +190,8 @@ struct simplex_solver_settings_t { f_t cut_min_orthogonality; // minimum orthogonality for cuts i_t mip_batch_pdlp_strong_branching{0}; // 0 if not using batch PDLP for strong branching, 1 if // using batch PDLP for strong branching + f_t bnb_work_unit_scale{1.0}; + bool gpu_heur_wait_for_exploration{true}; diving_heuristics_settings_t diving_settings; // Settings for the diving heuristics @@ -201,7 +204,9 @@ struct simplex_solver_settings_t { i_t inside_mip; // 0 if outside MIP, 1 if inside MIP at root node, 2 if inside MIP at leaf node i_t sub_mip; // 0 if in regular MIP solve, 1 if in sub-MIP solve - std::function&, f_t)> solution_callback; + std::function&, f_t, const cuopt::internals::mip_solution_callback_info_t&, double)> + new_incumbent_callback; std::function&, f_t)> node_processed_callback; std::function heuristic_preemption_callback; std::function&, std::vector&, f_t)> set_simplex_solution_callback; diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index a60d508fac..586866fe00 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cu @@ -102,7 +102,7 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_MIP_BATCH_PDLP_STRONG_BRANCHING, &mip_settings.mip_batch_pdlp_strong_branching, 0, 1, 0}, {CUOPT_PRESOLVE, reinterpret_cast(&pdlp_settings.presolver), CUOPT_PRESOLVE_DEFAULT, CUOPT_PRESOLVE_PSLP, CUOPT_PRESOLVE_DEFAULT}, {CUOPT_PRESOLVE, reinterpret_cast(&mip_settings.presolver), CUOPT_PRESOLVE_DEFAULT, CUOPT_PRESOLVE_PSLP, CUOPT_PRESOLVE_DEFAULT}, - {CUOPT_MIP_DETERMINISM_MODE, &mip_settings.determinism_mode, CUOPT_MODE_OPPORTUNISTIC, CUOPT_MODE_DETERMINISTIC, CUOPT_MODE_OPPORTUNISTIC}, + {CUOPT_MIP_DETERMINISM_MODE, &mip_settings.determinism_mode, CUOPT_DETERMINISM_NONE, CUOPT_DETERMINISM_FULL, CUOPT_DETERMINISM_NONE}, {CUOPT_RANDOM_SEED, &mip_settings.seed, -1, std::numeric_limits::max(), -1}, {CUOPT_MIP_RELIABILITY_BRANCHING, &mip_settings.reliability_branching, -1, std::numeric_limits::max(), -1}, {CUOPT_PDLP_PRECISION, reinterpret_cast(&pdlp_settings.pdlp_precision), CUOPT_PDLP_DEFAULT_PRECISION, CUOPT_PDLP_MIXED_PRECISION, CUOPT_PDLP_DEFAULT_PRECISION} diff --git a/cpp/src/mip_heuristics/diversity/diversity_config.hpp b/cpp/src/mip_heuristics/diversity/diversity_config.hpp index de14260794..b608839539 100644 --- a/cpp/src/mip_heuristics/diversity/diversity_config.hpp +++ b/cpp/src/mip_heuristics/diversity/diversity_config.hpp @@ -30,6 +30,10 @@ struct diversity_config_t { double lp_run_time_if_feasible = 2.; double lp_run_time_if_infeasible = 1.; bool halve_population = false; + bool fj_only_run = false; + bool dry_run = false; + bool initial_solution_only = false; + int n_fp_iterations = 1000000; }; } // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip_heuristics/diversity/diversity_manager.cu b/cpp/src/mip_heuristics/diversity/diversity_manager.cu index 0ded8337d8..2ee17090c0 100644 --- a/cpp/src/mip_heuristics/diversity/diversity_manager.cu +++ b/cpp/src/mip_heuristics/diversity/diversity_manager.cu @@ -5,7 +5,13 @@ */ /* clang-format on */ -#include "cuda_profiler_api.h" +// uncomment to enable detailed detemrinism logs +#undef CUOPT_DETERMINISM_LOG +#define CUOPT_DETERMINISM_LOG(...) \ + do { \ + CUOPT_LOG_INFO(__VA_ARGS__); \ + } while (0) + #include "diversity_manager.cuh" #include @@ -14,13 +20,13 @@ #include #include #include +#include #include +#include #include -#include - constexpr bool fj_only_run = false; namespace cuopt::linear_programming::detail { @@ -55,7 +61,7 @@ diversity_manager_t::diversity_manager_t(mip_solver_context_thandle_ptr->get_stream()), ls(context, lp_optimal_solution), rins(context, *this), - timer(diversity_config.default_time_limit), + timer(0.0, cuopt::termination_checker_t::root_tag_t{}), bound_prop_recombiner(context, context.problem_ptr->n_variables, ls.constraint_prop, @@ -79,6 +85,30 @@ diversity_manager_t::diversity_manager_t(mip_solver_context_t::n_of_arms, cuopt::seed_generator::get_seed(), ls_alpha, "ls"), ls_hash_map(*context.problem_ptr) { + fp_recombiner_config_t::max_n_of_vars_from_other = + fp_recombiner_config_t::initial_n_of_vars_from_other; + ls_recombiner_config_t::max_n_of_vars_from_other = + ls_recombiner_config_t::initial_n_of_vars_from_other; + bp_recombiner_config_t::max_n_of_vars_from_other = + bp_recombiner_config_t::initial_n_of_vars_from_other; + sub_mip_recombiner_config_t::max_n_of_vars_from_other = + sub_mip_recombiner_config_t::initial_n_of_vars_from_other; + mab_ls_config_t::last_lm_config = 0; + mab_ls_config_t::last_ls_mab_option = 0; + + CUOPT_DETERMINISM_LOG( + "Deterministic solve start diversity state: seed_state=%lld fp_max=%zu " + "ls_max=%zu bp_max=%zu sub_mip_max=%zu last_lm=%d last_ls=%d " + "enabled_recombiners=%zu", + (long long)cuopt::seed_generator::peek_seed(), + fp_recombiner_config_t::max_n_of_vars_from_other, + ls_recombiner_config_t::max_n_of_vars_from_other, + bp_recombiner_config_t::max_n_of_vars_from_other, + sub_mip_recombiner_config_t::max_n_of_vars_from_other, + (int)mab_ls_config_t::last_lm_config, + (int)mab_ls_config_t::last_ls_mab_option, + recombiner_t::enabled_recombiners.size()); + int max_config = -1; int env_config_id = -1; const char* env_max_config = std::getenv("CUOPT_MAX_CONFIG"); @@ -106,13 +136,16 @@ diversity_manager_t::diversity_manager_t(mip_solver_context_t bool diversity_manager_t::run_local_search(solution_t& solution, const weight_t& weights, - timer_t& timer, + work_limit_timer_t& timer, ls_config_t& ls_config) { raft::common::nvtx::range fun_scope("run_local_search"); @@ -133,7 +166,7 @@ void diversity_manager_t::generate_solution(f_t time_limit, bool rando sol.compute_feasibility(); // if a feasible is found, it is added to the population ls.generate_solution(sol, random_start, &population, time_limit); - population.add_solution(std::move(sol)); + population.add_solution(std::move(sol), internals::mip_solution_origin_t::LOCAL_SEARCH); } template @@ -146,7 +179,12 @@ void diversity_manager_t::add_user_given_solutions( rmm::device_uvector init_sol_assignment(*init_sol, sol.handle_ptr->get_stream()); if (problem_ptr->pre_process_assignment(init_sol_assignment)) { relaxed_lp_settings_t lp_settings; - lp_settings.time_limit = std::min(60., timer.remaining_time() / 2); + lp_settings.time_limit = std::min(60., timer.remaining_time() / 2); + if (timer.deterministic) { + lp_settings.work_limit = lp_settings.time_limit; + lp_settings.work_context = timer.work_context; + cuopt_assert(lp_settings.work_context != nullptr, "Missing deterministic work context"); + } lp_settings.tolerance = problem_ptr->tolerances.absolute_tolerance; lp_settings.save_state = false; lp_settings.return_first_feasible = true; @@ -165,7 +203,6 @@ void diversity_manager_t::add_user_given_solutions( is_feasible, sol.get_user_objective(), sol.get_total_excess()); - population.run_solution_callbacks(sol); initial_sol_vector.emplace_back(std::move(sol)); } else { CUOPT_LOG_ERROR( @@ -179,11 +216,13 @@ void diversity_manager_t::add_user_given_solutions( } template -bool diversity_manager_t::run_presolve(f_t time_limit, timer_t global_timer) +bool diversity_manager_t::run_presolve(f_t time_limit, + cuopt::termination_checker_t& global_timer) { raft::common::nvtx::range fun_scope("run_presolve"); CUOPT_LOG_INFO("Running presolve!"); - timer_t presolve_timer(time_limit); + CUOPT_LOG_INFO("Problem fingerprint before DM presolve: 0x%x", problem_ptr->get_fingerprint()); + work_limit_timer_t presolve_timer(context.gpu_heur_loop, time_limit, *context.termination); auto term_crit = ls.constraint_prop.bounds_update.solve(*problem_ptr); if (ls.constraint_prop.bounds_update.infeas_constraints_count > 0) { stats.presolve_time = timer.elapsed_time(); @@ -192,15 +231,14 @@ bool diversity_manager_t::run_presolve(f_t time_limit, timer_t global_ if (termination_criterion_t::NO_UPDATE != term_crit) { ls.constraint_prop.bounds_update.set_updated_bounds(*problem_ptr); } + bool run_probing_cache = !fj_only_run; - // Don't run probing cache in deterministic mode yet as neither B&B nor CPUFJ need it - // and it doesn't make use of work units yet - if (context.settings.determinism_mode == CUOPT_MODE_DETERMINISTIC) { run_probing_cache = false; } if (run_probing_cache) { // Run probing cache before trivial presolve to discover variable implications const f_t max_time_on_probing = diversity_config.max_time_on_probing; f_t time_for_probing_cache = std::min(max_time_on_probing, time_limit); - timer_t probing_timer{time_for_probing_cache}; + work_limit_timer_t probing_timer( + context.gpu_heur_loop, time_for_probing_cache, *context.termination); // this function computes probing cache, finds singletons, substitutions and changes the problem bool problem_is_infeasible = compute_probing_cache(ls.constraint_prop.bounds_update, *problem_ptr, probing_timer); @@ -209,20 +247,18 @@ bool diversity_manager_t::run_presolve(f_t time_limit, timer_t global_ const bool remap_cache_ids = true; if (!global_timer.check_time_limit()) { trivial_presolve(*problem_ptr, remap_cache_ids); } if (!problem_ptr->empty && !check_bounds_sanity(*problem_ptr)) { return false; } - // if (!presolve_timer.check_time_limit() && !context.settings.heuristics_only && - // !problem_ptr->empty) { + const bool run_clique_table = + !presolve_timer.check_time_limit() && !context.settings.heuristics_only && + !problem_ptr->empty && !(context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS); + // if (run_clique_table) { // f_t time_limit_for_clique_table = std::min(3., presolve_timer.remaining_time() / 5); // timer_t clique_timer(time_limit_for_clique_table); // dual_simplex::user_problem_t host_problem(problem_ptr->handle_ptr); // problem_ptr->get_host_user_problem(host_problem); // std::shared_ptr> clique_table; // constexpr bool modify_problem_with_cliques = false; - // find_initial_cliques(host_problem, - // context.settings.tolerances, - // &clique_table, - // clique_timer, - // modify_problem_with_cliques, - // nullptr); + // find_initial_cliques( + // host_problem, context.settings.tolerances, clique_timer, modify_problem_with_cliques); // if (modify_problem_with_cliques) { // problem_ptr->set_constraints_from_host_user_problem(host_problem); // cuopt_assert(host_problem.lower.size() == static_cast(problem_ptr->n_variables), @@ -249,6 +285,10 @@ bool diversity_manager_t::run_presolve(f_t time_limit, timer_t global_ } stats.presolve_time = presolve_timer.elapsed_time(); lp_optimal_solution.resize(problem_ptr->n_variables, problem_ptr->handle_ptr->get_stream()); + thrust::fill(problem_ptr->handle_ptr->get_thrust_policy(), + lp_optimal_solution.begin(), + lp_optimal_solution.end(), + f_t(0)); lp_dual_optimal_solution.resize(problem_ptr->n_constraints, problem_ptr->handle_ptr->get_stream()); problem_ptr->handle_ptr->sync_stream(); @@ -256,7 +296,9 @@ bool diversity_manager_t::run_presolve(f_t time_limit, timer_t global_ problem_ptr->n_constraints, problem_ptr->n_variables, problem_ptr->presolve_data.objective_offset); - CUOPT_LOG_INFO("cuOpt presolve time: %.2f", stats.presolve_time); + CUOPT_LOG_INFO("cuOpt presolve time: %.2f, fingerprint: 0x%x", + stats.presolve_time, + problem_ptr->get_fingerprint()); return true; } @@ -268,24 +310,21 @@ void diversity_manager_t::generate_quick_feasible_solution() // min 1 second, max 10 seconds const f_t generate_fast_solution_time = std::min(diversity_config.max_fast_sol_time, std::max(1., timer.remaining_time() / 20.)); - timer_t sol_timer(generate_fast_solution_time); + work_limit_timer_t sol_timer( + context.gpu_heur_loop, generate_fast_solution_time, *context.termination); // do very short LP run to get somewhere close to the optimal point ls.generate_fast_solution(solution, sol_timer); if (solution.get_feasible()) { - population.run_solution_callbacks(solution); initial_sol_vector.emplace_back(std::move(solution)); problem_ptr->handle_ptr->sync_stream(); solution_t searched_sol(initial_sol_vector.back()); ls_config_t ls_config; run_local_search(searched_sol, population.weights, sol_timer, ls_config); - population.run_solution_callbacks(searched_sol); initial_sol_vector.emplace_back(std::move(searched_sol)); auto& feas_sol = initial_sol_vector.back().get_feasible() ? initial_sol_vector.back() : initial_sol_vector[initial_sol_vector.size() - 2]; - CUOPT_LOG_INFO("Generated fast solution in %f seconds with objective %f", - timer.elapsed_time(), - feas_sol.get_user_objective()); + CUOPT_LOG_INFO("Generated fast solution with objective %f", feas_sol.get_user_objective()); } problem_ptr->handle_ptr->sync_stream(); } @@ -314,6 +353,10 @@ void diversity_manager_t::run_fj_alone(solution_t& solution) ls.fj.settings.feasibility_run = false; ls.fj.settings.time_limit = timer.remaining_time(); ls.fj.solve(solution); + if (solution.get_feasible()) { + population.add_solution(std::move(solution), + internals::mip_solution_origin_t::FEASIBILITY_JUMP); + } CUOPT_LOG_INFO("FJ alone finished!"); } @@ -321,10 +364,34 @@ void diversity_manager_t::run_fj_alone(solution_t& solution) template void diversity_manager_t::run_fp_alone() { - CUOPT_LOG_DEBUG("Running FP alone!"); + CUOPT_DETERMINISM_LOG("Deterministic FP alone enter"); solution_t sol(population.best_feasible()); - ls.run_fp(sol, timer, &population); - CUOPT_LOG_DEBUG("FP alone finished!"); + sol.handle_ptr->sync_stream(); + CUOPT_DETERMINISM_LOG( + "Deterministic FP alone input: hash=0x%x feasible=%d obj=%.16e excess=%.16e", + sol.get_hash(), + (int)sol.get_feasible(), + sol.get_user_objective(), + sol.get_total_excess()); + ls.run_fp(sol, timer, &population, diversity_config.n_fp_iterations); + sol.handle_ptr->sync_stream(); + CUOPT_DETERMINISM_LOG( + "Deterministic FP alone output: hash=0x%x feasible=%d obj=%.16e excess=%.16e", + sol.get_hash(), + (int)sol.get_feasible(), + sol.get_user_objective(), + sol.get_total_excess()); + if (sol.get_feasible()) { + population.add_solution(std::move(sol), internals::mip_solution_origin_t::LOCAL_SEARCH); + } + auto& best_sol = population.best_feasible(); + best_sol.handle_ptr->sync_stream(); + CUOPT_DETERMINISM_LOG( + "Deterministic FP alone population best after: hash=0x%x feasible=%d obj=%.16e excess=%.16e", + best_sol.get_hash(), + (int)best_sol.get_feasible(), + best_sol.get_user_objective(), + best_sol.get_total_excess()); } template @@ -341,49 +408,77 @@ solution_t diversity_manager_t::run_solver() raft::common::nvtx::range fun_scope("run_solver"); CUOPT_LOG_DEBUG("Determinism mode: %s", - context.settings.determinism_mode == CUOPT_MODE_DETERMINISTIC ? "deterministic" - : "opportunistic"); + (context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS) + ? "deterministic" + : "opportunistic"); // to automatically compute the solving time on scope exit auto timer_raii_guard = cuopt::scope_guard([&]() { stats.total_solve_time = timer.elapsed_time(); }); + auto log_return_solution = [&](const char* reason, solution_t& sol) { + sol.handle_ptr->sync_stream(); + CUOPT_DETERMINISM_LOG( + "Deterministic run_solver return: reason=%s hash=0x%x feasible=%d " + "obj=%.16e excess=%.16e", + reason, + sol.get_hash(), + (int)sol.get_feasible(), + sol.get_user_objective(), + sol.get_total_excess()); + }; - // Debug: Allow disabling GPU heuristics to test B&B tree determinism in isolation + const bool deterministic_bb_without_deterministic_heuristics = + (context.settings.determinism_mode & CUOPT_DETERMINISM_BB) && + !(context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS); const char* disable_heuristics_env = std::getenv("CUOPT_DISABLE_GPU_HEURISTICS"); - if (context.settings.determinism_mode == CUOPT_MODE_DETERMINISTIC) { - CUOPT_LOG_INFO("Running deterministic mode with CPUFJ heuristic"); + if (deterministic_bb_without_deterministic_heuristics || + (disable_heuristics_env != nullptr && std::string(disable_heuristics_env) == "1")) { + CUOPT_LOG_INFO("GPU heuristics disabled (det_bb_only=%d env=%s)", + (int)deterministic_bb_without_deterministic_heuristics, + disable_heuristics_env ? disable_heuristics_env : "unset"); + if ((context.settings.determinism_mode & CUOPT_DETERMINISM_BB) && + context.branch_and_bound_ptr != nullptr) { + auto& producer_sync = context.branch_and_bound_ptr->get_producer_sync(); + producer_sync.registration_complete(); + } population.initialize_population(); population.allocate_solutions(); - // Start CPUFJ in deterministic mode with B&B integration - if (context.branch_and_bound_ptr != nullptr) { - ls.start_cpufj_deterministic(*context.branch_and_bound_ptr); - } - while (!check_b_b_preemption()) { - if (timer.check_time_limit()) break; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } - - // Stop CPUFJ when B&B is done - ls.stop_cpufj_deterministic(); - - population.add_external_solutions_to_population(); - return population.best_feasible(); + auto& best_sol = population.best_feasible(); + log_return_solution("heuristics_disabled", best_sol); + return best_sol; } - if (disable_heuristics_env != nullptr && std::string(disable_heuristics_env) == "1") { - CUOPT_LOG_INFO("GPU heuristics disabled via CUOPT_DISABLE_GPU_HEURISTICS=1"); - population.initialize_population(); - population.allocate_solutions(); - while (!check_b_b_preemption()) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + bool gpu_heuristic_producer_registered = false; + auto gpu_heuristic_producer_guard = cuopt::scope_guard([&]() { + if (!gpu_heuristic_producer_registered || context.branch_and_bound_ptr == nullptr) { return; } + auto& producer_sync = context.branch_and_bound_ptr->get_producer_sync(); + producer_sync.deregister_producer(context.gpu_heur_loop.producer_progress_ptr()); + context.gpu_heur_loop.detach_producer_sync(); + }); + if ((context.settings.determinism_mode & CUOPT_DETERMINISM_BB) && + context.branch_and_bound_ptr != nullptr) { + if (context.settings.gpu_heur_wait_for_exploration) { + CUOPT_LOG_INFO("GPU heuristics waiting for B&B tree exploration to start..."); + auto wait_start = std::chrono::high_resolution_clock::now(); + context.branch_and_bound_ptr->wait_for_exploration_start(); + double wait_elapsed = + std::chrono::duration(std::chrono::high_resolution_clock::now() - wait_start) + .count(); + CUOPT_LOG_INFO("GPU heuristics resumed after %.2fs (B&B exploration started)", wait_elapsed); } - return population.best_feasible(); + auto& producer_sync = context.branch_and_bound_ptr->get_producer_sync(); + context.gpu_heur_loop.attach_producer_sync(&producer_sync); + producer_sync.register_producer(context.gpu_heur_loop.producer_progress_ptr()); + producer_sync.registration_complete(); + gpu_heuristic_producer_registered = true; } population.timer = timer; - const f_t time_limit = timer.remaining_time(); + const f_t time_limit = timer.deterministic ? timer.get_time_limit() : timer.remaining_time(); const f_t lp_time_limit = std::min(diversity_config.max_time_on_lp, time_limit * diversity_config.time_ratio_on_init_lp); // after every change to the problem, we should resize all the relevant vars @@ -394,7 +489,7 @@ solution_t diversity_manager_t::run_solver() problem_ptr->check_problem_representation(true); // have the structure ready for reusing later problem_ptr->compute_integer_fixed_problem(); - recombiner_t::init_enabled_recombiners(*problem_ptr); + recombiner_t::init_enabled_recombiners(context, *problem_ptr); mab_recombiner.resize_mab_arm_stats(recombiner_t::enabled_recombiners.size()); // test problem is not ii cuopt_func_call( @@ -404,20 +499,34 @@ solution_t diversity_manager_t::run_solver() "The problem must not be ii"); population.initialize_population(); population.allocate_solutions(); - if (check_b_b_preemption()) { return population.best_feasible(); } + if (check_b_b_preemption()) { + auto& best_sol = population.best_feasible(); + log_return_solution("preempted_after_population_init", best_sol); + return best_sol; + } add_user_given_solutions(initial_sol_vector); + CUOPT_LOG_DEBUG("DM bootstrap: initial_sol_vector size after user solutions = %lu", + initial_sol_vector.size()); // Run CPUFJ early to find quick initial solutions ls_cpufj_raii_guard_t ls_cpufj_raii_guard(ls); // RAII to stop cpufj threads on solve stop - ls.start_cpufj_scratch_threads(population); - if (check_b_b_preemption()) { return population.best_feasible(); } + if (!diversity_config.dry_run && + !(context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS)) { + ls.start_cpufj_scratch_threads(population); + } + + if (check_b_b_preemption()) { + auto& best_sol = population.best_feasible(); + log_return_solution("preempted_before_lp", best_sol); + return best_sol; + } lp_state_t& lp_state = problem_ptr->lp_state; // resize because some constructor might be called before the presolve lp_state.resize(*problem_ptr, problem_ptr->handle_ptr->get_stream()); bool bb_thread_solution_exists = simplex_solution_exists.load(); if (bb_thread_solution_exists) { ls.lp_optimal_exists = true; - } else if (!fj_only_run) { + } else if (!diversity_config.fj_only_run) { convert_greater_to_less(*problem_ptr); f_t tolerance_divisor = @@ -425,20 +534,49 @@ solution_t diversity_manager_t::run_solver() if (tolerance_divisor == 0) { tolerance_divisor = 1; } f_t absolute_tolerance = context.settings.tolerances.absolute_tolerance; - pdlp_solver_settings_t pdlp_settings{}; - pdlp_settings.tolerances.relative_primal_tolerance = absolute_tolerance / tolerance_divisor; - pdlp_settings.tolerances.relative_dual_tolerance = absolute_tolerance / tolerance_divisor; - pdlp_settings.time_limit = lp_time_limit; - pdlp_settings.first_primal_feasible = false; - pdlp_settings.concurrent_halt = &global_concurrent_halt; - pdlp_settings.method = method_t::Concurrent; - pdlp_settings.inside_mip = true; - pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; - pdlp_settings.num_gpus = context.settings.num_gpus; - pdlp_settings.presolver = presolver_t::None; - - timer_t lp_timer(lp_time_limit); - auto lp_result = solve_lp_with_method(*problem_ptr, pdlp_settings, lp_timer); + auto lp_result = [&]() { + if (timer.deterministic) { + relaxed_lp_settings_t lp_settings{}; + lp_settings.time_limit = lp_time_limit; + lp_settings.work_limit = lp_time_limit; + lp_settings.tolerance = absolute_tolerance; + lp_settings.check_infeasibility = true; + lp_settings.return_first_feasible = false; + lp_settings.save_state = true; + lp_settings.per_constraint_residual = true; + lp_settings.has_initial_primal = false; + lp_settings.concurrent_halt = &global_concurrent_halt; + lp_settings.work_context = &context.gpu_heur_loop; + cuopt_assert(lp_settings.work_context != nullptr, "Missing deterministic work context"); + CUOPT_DETERMINISM_LOG( + "DM root LP config: dry_run=%d deterministic=%d work_limit=%.6f time_limit=%.6f", + (int)diversity_config.dry_run, + (int)timer.deterministic, + lp_settings.work_limit, + lp_settings.time_limit); + return get_relaxed_lp_solution( + *problem_ptr, lp_optimal_solution, lp_state, lp_settings); + } + pdlp_solver_settings_t pdlp_settings{}; + pdlp_settings.tolerances.relative_primal_tolerance = absolute_tolerance / tolerance_divisor; + pdlp_settings.tolerances.relative_dual_tolerance = absolute_tolerance / tolerance_divisor; + pdlp_settings.time_limit = lp_time_limit; + pdlp_settings.first_primal_feasible = false; + pdlp_settings.concurrent_halt = &global_concurrent_halt; + pdlp_settings.method = method_t::Concurrent; + pdlp_settings.inside_mip = true; + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; + pdlp_settings.num_gpus = context.settings.num_gpus; + pdlp_settings.presolver = presolver_t::None; + timer_t lp_timer(lp_time_limit); + return solve_lp_with_method(*problem_ptr, pdlp_settings, lp_timer); + }(); + CUOPT_DETERMINISM_LOG( + "DM root LP result: status=%d iters=%d user_obj=%.12f primal_hash=0x%x", + (int)lp_result.get_termination_status(), + lp_result.get_additional_termination_information().number_of_steps_taken, + lp_result.get_objective_value(), + detail::compute_hash(lp_result.get_primal_solution(), problem_ptr->handle_ptr->get_stream())); { std::lock_guard guard(relaxed_solution_mutex); @@ -482,9 +620,10 @@ solution_t diversity_manager_t::run_solver() } else if (lp_result.get_termination_status() == pdlp_termination_status_t::DualInfeasible) { CUOPT_LOG_ERROR("PDLP detected dual infeasibility, continuing anyway!"); ls.lp_optimal_exists = false; - } else if (lp_result.get_termination_status() == pdlp_termination_status_t::TimeLimit) { + } else if (lp_result.get_termination_status() == pdlp_termination_status_t::TimeLimit || + lp_result.get_termination_status() == pdlp_termination_status_t::IterationLimit) { CUOPT_LOG_DEBUG( - "Initial LP run exceeded time limit, continuing solver with partial LP result!"); + "Initial LP run exceeded time/iteration limit, continuing solver with partial LP result!"); // note to developer, in debug mode the LP run might be too slow and it might cause PDLP not // to bring variables within the bounds } @@ -523,43 +662,89 @@ solution_t diversity_manager_t::run_solver() // in case the pdlp returned var boudns that are out of bounds clamp_within_var_bounds(lp_optimal_solution, problem_ptr, problem_ptr->handle_ptr); + CUOPT_DETERMINISM_LOG( + "DM root LP post-clamp: lp_optimal_solution hash=0x%x", + detail::compute_hash(lp_optimal_solution, problem_ptr->handle_ptr->get_stream())); } if (ls.lp_optimal_exists) { solution_t lp_rounded_sol(*problem_ptr); lp_rounded_sol.copy_new_assignment(lp_optimal_solution); + CUOPT_LOG_DEBUG("DM bootstrap candidate (LP raw): hash=0x%x feas=%d obj=%.12f", + lp_rounded_sol.get_hash(), + (int)lp_rounded_sol.get_feasible(), + lp_rounded_sol.get_user_objective()); lp_rounded_sol.round_nearest(); lp_rounded_sol.compute_feasibility(); - population.add_solution(std::move(lp_rounded_sol)); - ls.start_cpufj_lptopt_scratch_threads(population); + CUOPT_LOG_DEBUG("DM bootstrap candidate (LP rounded): hash=0x%x feas=%d obj=%.12f", + lp_rounded_sol.get_hash(), + (int)lp_rounded_sol.get_feasible(), + lp_rounded_sol.get_user_objective()); + population.add_solution(std::move(lp_rounded_sol), + internals::mip_solution_origin_t::LP_ROUNDING); + if (!diversity_config.dry_run && + !(context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS)) { + ls.start_cpufj_lptopt_scratch_threads(population); + } } - population.add_solutions_from_vec(std::move(initial_sol_vector)); + for (size_t i = 0; i < initial_sol_vector.size(); ++i) { + CUOPT_LOG_DEBUG("DM bootstrap candidate (initial_sol_vector[%lu]): hash=0x%x feas=%d obj=%.12f", + i, + initial_sol_vector[i].get_hash(), + (int)initial_sol_vector[i].get_feasible(), + initial_sol_vector[i].get_user_objective()); + } + population.add_solutions_from_vec(std::move(initial_sol_vector), + internals::mip_solution_origin_t::USER_INITIAL); - if (check_b_b_preemption()) { return population.best_feasible(); } + if (check_b_b_preemption()) { + auto& best_sol = population.best_feasible(); + log_return_solution("preempted_after_initial_population", best_sol); + return best_sol; + } if (context.settings.benchmark_info_ptr != nullptr) { context.settings.benchmark_info_ptr->objective_of_initial_population = population.best_feasible().get_user_objective(); } - if (fj_only_run) { + if (diversity_config.dry_run) { + auto& best_sol = population.best_feasible(); + log_return_solution("dry_run", best_sol); + return best_sol; + } + if (diversity_config.fj_only_run) { solution_t sol(*problem_ptr); run_fj_alone(sol); + log_return_solution("fj_only_run", sol); return sol; } - rins.enable(); + if (!(context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS)) { rins.enable(); } generate_solution(timer.remaining_time(), false); - if (timer.check_time_limit()) { + if (diversity_config.initial_solution_only) { + auto& best_sol = population.best_feasible(); + log_return_solution("initial_solution_only", best_sol); + return best_sol; + } + if (work_limit_reached()) { population.add_external_solutions_to_population(); - return population.best_feasible(); + auto& best_sol = population.best_feasible(); + log_return_solution("work_limit_reached", best_sol); + return best_sol; + } + if (check_b_b_preemption()) { + auto& best_sol = population.best_feasible(); + log_return_solution("preempted_before_fp", best_sol); + return best_sol; } - if (check_b_b_preemption()) { return population.best_feasible(); } run_fp_alone(); population.add_external_solutions_to_population(); - return population.best_feasible(); + auto& best_sol = population.best_feasible(); + log_return_solution("post_fp_alone", best_sol); + return best_sol; }; template @@ -579,13 +764,15 @@ void diversity_manager_t::diversity_step(i_t max_iterations_without_im CUOPT_LOG_DEBUG("Population degenerated in diversity step"); return; } - if (timer.check_time_limit()) return; + if (work_limit_reached()) return; constexpr bool tournament = true; auto [sol1, sol2] = population.get_two_random(tournament); cuopt_assert(population.test_invariant(), ""); auto [lp_offspring, offspring] = recombine_and_local_search(sol1, sol2); - auto [inserted_pos_1, best_updated_1] = population.add_solution(std::move(lp_offspring)); - auto [inserted_pos_2, best_updated_2] = population.add_solution(std::move(offspring)); + auto [inserted_pos_1, best_updated_1] = population.add_solution( + std::move(lp_offspring), internals::mip_solution_origin_t::RECOMBINATION); + auto [inserted_pos_2, best_updated_2] = population.add_solution( + std::move(offspring), internals::mip_solution_origin_t::RECOMBINATION); if (best_updated_1 || best_updated_2) { recombine_stats.add_best_updated(); } cuopt_assert(population.test_invariant(), ""); if ((inserted_pos_1 != -1 && inserted_pos_1 <= 2) || @@ -627,12 +814,14 @@ void diversity_manager_t::recombine_and_ls_with_all(solution_t::recombine_and_ls_with_all(solution_t void diversity_manager_t::recombine_and_ls_with_all( - std::vector>& solutions, bool add_only_feasible) + std::vector::drained_external_solution_t>& solutions, + bool add_only_feasible) { raft::common::nvtx::range fun_scope("recombine_and_ls_with_all"); if (solutions.size() > 0) { CUOPT_LOG_DEBUG("Running recombiners on B&B solutions with size %lu", solutions.size()); // add all solutions because time limit might have been consumed and we might have exited before - for (auto& sol : solutions) { + for (auto& drained_sol : solutions) { + auto& sol = drained_sol.solution; cuopt_func_call(sol.test_feasibility(true)); - population.add_solution(std::move(solution_t(sol))); + population.add_solution(std::move(solution_t(sol)), drained_sol.origin); } - for (auto& sol : solutions) { - if (timer.check_time_limit()) { return; } + for (auto& drained_sol : solutions) { + auto& sol = drained_sol.solution; + if (work_limit_reached()) { return; } solution_t ls_solution(sol); ls_config_t ls_config; run_local_search(ls_solution, population.weights, timer, ls_config); - if (timer.check_time_limit()) { return; } + if (work_limit_reached()) { return; } // TODO try if running LP with integers fixed makes it feasible if (ls_solution.get_feasible()) { CUOPT_LOG_DEBUG("LS searched solution feasible, running recombiners!"); @@ -702,6 +894,7 @@ diversity_manager_t::recombine_and_local_search(solution_t& sol1.get_feasible(), sol2.get_quality(population.weights), sol2.get_feasible()); + bool deterministic = (context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS); double best_objective_of_parents = std::min(sol1.get_objective(), sol2.get_objective()); bool at_least_one_parent_feasible = sol1.get_feasible() || sol2.get_feasible(); // randomly choose among 3 recombiners @@ -712,7 +905,7 @@ diversity_manager_t::recombine_and_local_search(solution_t& std::numeric_limits::lowest(), std::numeric_limits::lowest(), std::numeric_limits::max(), - recombiner_work_normalized_reward_t(0.0)); + recombiner_work_normalized_reward_t(deterministic, 0.0)); return std::make_pair(solution_t(sol1), solution_t(sol2)); } cuopt_assert(population.test_invariant(), ""); @@ -732,7 +925,7 @@ diversity_manager_t::recombine_and_local_search(solution_t& std::numeric_limits::lowest(), std::numeric_limits::lowest(), std::numeric_limits::max(), - recombiner_work_normalized_reward_t(0.0)); + recombiner_work_normalized_reward_t(deterministic, 0.0)); return std::make_pair(solution_t(sol1), solution_t(sol2)); } cuopt_assert(offspring.test_number_all_integer(), "All must be integers after LS"); @@ -750,7 +943,12 @@ diversity_manager_t::recombine_and_local_search(solution_t& : diversity_config.lp_run_time_if_infeasible; lp_run_time = std::min(lp_run_time, timer.remaining_time()); relaxed_lp_settings_t lp_settings; - lp_settings.time_limit = lp_run_time; + lp_settings.time_limit = lp_run_time; + if (timer.deterministic) { + lp_settings.work_limit = lp_settings.time_limit; + lp_settings.work_context = timer.work_context; + cuopt_assert(lp_settings.work_context != nullptr, "Missing deterministic work context"); + } lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; lp_settings.return_first_feasible = false; lp_settings.save_state = true; @@ -771,12 +969,15 @@ diversity_manager_t::recombine_and_local_search(solution_t& offspring_qual, sol1.get_quality(population.weights), sol2.get_quality(population.weights)); f_t best_quality_of_parents = std::min(sol1.get_quality(population.weights), sol2.get_quality(population.weights)); - mab_recombiner.add_mab_reward( - mab_recombiner.last_chosen_option, - best_quality_of_parents, - population.best().get_quality(population.weights), - offspring_qual, - recombiner_work_normalized_reward_t(recombine_stats.get_last_recombiner_time())); + mab_recombiner.add_mab_reward(mab_recombiner.last_chosen_option, + best_quality_of_parents, + population.best().get_quality(population.weights), + offspring_qual, + !deterministic + ? recombiner_work_normalized_reward_t( + deterministic, recombine_stats.get_last_recombiner_time()) + : recombiner_work_normalized_reward_t( + deterministic, recombine_stats.get_last_recombiner_work())); mab_ls.add_mab_reward(mab_ls_config_t::last_ls_mab_option, best_quality_of_parents, population.best_feasible().get_quality(population.weights), @@ -821,31 +1022,49 @@ std::pair, bool> diversity_manager_t::recombine( } } } + CUOPT_DETERMINISM_LOG( + "Deterministic recombiner selection: requested=%s selected_index=%d chosen=%s " + "enabled_size=%zu last_choice_before=%d", + recombiner_t::recombiner_name(recombiner_type), + (int)selected_index, + recombiner_t::recombiner_name(recombiner), + recombiner_t::enabled_recombiners.size(), + mab_recombiner.last_chosen_option); mab_recombiner.set_last_chosen_option(selected_index); recombine_stats.add_attempt((recombiner_enum_t)recombiner); recombine_stats.start_recombiner_time(); + CUOPT_LOG_TRACE("Recombining sol %x and %x with recombiner %d, weights %x", + a.get_hash(), + b.get_hash(), + recombiner, + population.weights.get_hash()); + // Refactored code using a switch statement switch (recombiner) { case recombiner_enum_t::BOUND_PROP: { - auto [sol, success] = bound_prop_recombiner.recombine(a, b, population.weights); + auto [sol, success, work] = bound_prop_recombiner.recombine(a, b, population.weights); + recombine_stats.set_recombiner_work(work); recombine_stats.stop_recombiner_time(); if (success) { recombine_stats.add_success(); } return std::make_pair(sol, success); } case recombiner_enum_t::FP: { - auto [sol, success] = fp_recombiner.recombine(a, b, population.weights); + auto [sol, success, work] = fp_recombiner.recombine(a, b, population.weights); + recombine_stats.set_recombiner_work(work); recombine_stats.stop_recombiner_time(); if (success) { recombine_stats.add_success(); } return std::make_pair(sol, success); } case recombiner_enum_t::LINE_SEGMENT: { - auto [sol, success] = line_segment_recombiner.recombine(a, b, population.weights); + auto [sol, success, work] = line_segment_recombiner.recombine(a, b, population.weights); + recombine_stats.set_recombiner_work(work); recombine_stats.stop_recombiner_time(); if (success) { recombine_stats.add_success(); } return std::make_pair(sol, success); } case recombiner_enum_t::SUB_MIP: { - auto [sol, success] = sub_mip_recombiner.recombine(a, b, population.weights); + auto [sol, success, work] = sub_mip_recombiner.recombine(a, b, population.weights); + recombine_stats.set_recombiner_work(work); recombine_stats.stop_recombiner_time(); if (success) { recombine_stats.add_success(); } return std::make_pair(sol, success); @@ -891,6 +1110,12 @@ void diversity_manager_t::set_simplex_solution(const std::vector& context.handle_ptr->sync_stream(); } +template +bool diversity_manager_t::work_limit_reached() +{ + return timer.check_time_limit(); +} + #if MIP_INSTANTIATE_FLOAT template class diversity_manager_t; #endif diff --git a/cpp/src/mip_heuristics/diversity/diversity_manager.cuh b/cpp/src/mip_heuristics/diversity/diversity_manager.cuh index 4f86192db8..7f6e2fc741 100644 --- a/cpp/src/mip_heuristics/diversity/diversity_manager.cuh +++ b/cpp/src/mip_heuristics/diversity/diversity_manager.cuh @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -35,7 +36,7 @@ template class diversity_manager_t { public: diversity_manager_t(mip_solver_context_t& context); - bool run_presolve(f_t time_limit, timer_t global_timer); + bool run_presolve(f_t time_limit, cuopt::termination_checker_t& global_timer); solution_t run_solver(); void generate_solution(f_t time_limit, bool random_start = true); void run_fj_alone(solution_t& solution); @@ -50,8 +51,9 @@ class diversity_manager_t { void diversity_step(i_t max_iterations_without_improvement); void add_user_given_solutions(std::vector>& initial_sol_vector); population_t* get_population_pointer() { return &population; } - void recombine_and_ls_with_all(std::vector>& solutions, - bool add_only_feasible = false); + void recombine_and_ls_with_all( + std::vector::drained_external_solution_t>& solutions, + bool add_only_feasible = false); void recombine_and_ls_with_all(solution_t& solution, bool add_only_feasible = false); std::pair, solution_t> recombine_and_local_search( solution_t& a, @@ -65,8 +67,9 @@ class diversity_manager_t { solution_t& sol2); bool run_local_search(solution_t& solution, const weight_t& weights, - timer_t& timer, + work_limit_timer_t& timer, ls_config_t& ls_config); + bool work_limit_reached(); void set_simplex_solution(const std::vector& solution, const std::vector& dual_solution, @@ -80,7 +83,7 @@ class diversity_manager_t { rmm::device_uvector lp_dual_optimal_solution; std::atomic simplex_solution_exists{false}; local_search_t ls; - cuopt::timer_t timer; + cuopt::work_limit_timer_t timer; bound_prop_recombiner_t bound_prop_recombiner; fp_recombiner_t fp_recombiner; line_segment_recombiner_t line_segment_recombiner; diff --git a/cpp/src/mip_heuristics/diversity/lns/rins.cu b/cpp/src/mip_heuristics/diversity/lns/rins.cu index d7d7601014..6c94c159f2 100644 --- a/cpp/src/mip_heuristics/diversity/lns/rins.cu +++ b/cpp/src/mip_heuristics/diversity/lns/rins.cu @@ -270,10 +270,11 @@ void rins_t::run_rins() branch_and_bound_settings.sub_mip = 1; branch_and_bound_settings.log.log = false; branch_and_bound_settings.log.log_prefix = "[RINS] "; - branch_and_bound_settings.solution_callback = [&rins_solution_queue](std::vector& solution, - f_t objective) { - rins_solution_queue.push_back(solution); - }; + branch_and_bound_settings.new_incumbent_callback = + [&rins_solution_queue](std::vector& solution, + f_t objective, + const cuopt::internals::mip_solution_callback_info_t&, + double) { rins_solution_queue.push_back(solution); }; dual_simplex::branch_and_bound_t branch_and_bound( branch_and_bound_problem, branch_and_bound_settings, dual_simplex::tic()); branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment, rins_handle.get_stream())); @@ -343,8 +344,9 @@ void rins_t::run_rins() cuopt_assert(best_sol.assignment.size() == sol_size_before_rins, "Assignment size mismatch"); cuopt_assert(best_sol.assignment.size() == problem_copy->n_variables, "Assignment size mismatch"); - dm.population.add_external_solution( - best_sol.get_host_assignment(), best_sol.get_objective(), solution_origin_t::RINS); + dm.population.add_external_solution(best_sol.get_host_assignment(), + best_sol.get_objective(), + internals::mip_solution_origin_t::RINS); } } diff --git a/cpp/src/mip_heuristics/diversity/multi_armed_bandit.cuh b/cpp/src/mip_heuristics/diversity/multi_armed_bandit.cuh index 4571d0d57f..b9219b8dcb 100644 --- a/cpp/src/mip_heuristics/diversity/multi_armed_bandit.cuh +++ b/cpp/src/mip_heuristics/diversity/multi_armed_bandit.cuh @@ -45,16 +45,22 @@ struct ls_work_normalized_reward_t { }; struct recombiner_work_normalized_reward_t { - double time_in_miliseconds; - recombiner_work_normalized_reward_t(double time_in_miliseconds) - : time_in_miliseconds(time_in_miliseconds) + bool deterministic; + double work; + recombiner_work_normalized_reward_t(bool deterministic, double work) + : deterministic(deterministic), work(work) { } double operator()(double factor) const { // normal recombiners take 2000 ms - return factor * (std::max(0.1, 4.0 - (time_in_miliseconds / 2000))); + if (!deterministic) { + double time_in_miliseconds = work; + return factor * (std::max(0.1, 4.0 - (time_in_miliseconds / 2000))); + } else { + return factor * (std::max(0.1, 4.0 - (work / 200))); + } } }; diff --git a/cpp/src/mip_heuristics/diversity/population.cu b/cpp/src/mip_heuristics/diversity/population.cu index bca87223d9..f6965e744c 100644 --- a/cpp/src/mip_heuristics/diversity/population.cu +++ b/cpp/src/mip_heuristics/diversity/population.cu @@ -8,11 +8,14 @@ #include "diversity_manager.cuh" #include "population.cuh" +#include + #include #include #include #include #include +#include #include #include @@ -44,7 +47,7 @@ population_t::population_t(std::string const& name_, rng(cuopt::seed_generator::get_seed()), early_exit_primal_generation(false), population_hash_map(*problem_ptr), - timer(0) + timer(0.0, cuopt::termination_checker_t::root_tag_t{}) { best_feasible_objective = std::numeric_limits::max(); } @@ -125,11 +128,12 @@ std::pair, solution_t> population_t::ge } template -void population_t::add_solutions_from_vec(std::vector>&& solutions) +void population_t::add_solutions_from_vec( + std::vector>&& solutions, internals::mip_solution_origin_t callback_origin) { raft::common::nvtx::range fun_scope("add_solution_from_vec"); for (auto&& sol : solutions) { - add_solution(std::move(sol)); + add_solution(std::move(sol), callback_origin); } } @@ -143,11 +147,11 @@ size_t population_t::get_external_solution_size() template void population_t::add_external_solution(const std::vector& solution, f_t objective, - solution_origin_t origin) + internals::mip_solution_origin_t origin) { std::lock_guard lock(solution_mutex); - if (origin == solution_origin_t::CPUFJ) { + if (origin == internals::mip_solution_origin_t::CPU_FEASIBILITY_JUMP) { external_solution_queue_cpufj.emplace_back(solution, objective, origin); } else { external_solution_queue.emplace_back(solution, objective, origin); @@ -165,7 +169,7 @@ void population_t::add_external_solution(const std::vector& solut } CUOPT_LOG_DEBUG("%s added a solution to population, solution queue size %lu with objective %g", - solution_origin_to_string(origin), + internals::mip_solution_origin_to_string(origin), external_solution_queue.size(), problem_ptr->get_user_obj_from_solver_obj(objective)); if (objective < best_feasible_objective) { @@ -179,9 +183,12 @@ void population_t::add_external_solution(const std::vector& solut template void population_t::add_external_solutions_to_population() { + if ((context.settings.determinism_mode & CUOPT_DETERMINISM_BB)) { return; } // don't do early exit checks here. mutex needs to be acquired to prevent race conditions auto new_sol_vector = get_external_solutions(); - add_solutions_from_vec(std::move(new_sol_vector)); + for (auto& drained_sol : new_sol_vector) { + add_solution(std::move(drained_sol.solution), drained_sol.origin); + } } // normally we would need a lock here but these are boolean types and race conditions are not @@ -194,10 +201,11 @@ void population_t::preempt_heuristic_solver() } template -std::vector> population_t::get_external_solutions() +std::vector::drained_external_solution_t> +population_t::get_external_solutions() { std::lock_guard lock(solution_mutex); - std::vector> return_vector; + std::vector return_vector; i_t counter = 0; f_t new_best_feasible_objective = best_feasible_objective; f_t longest_wait_time = 0; @@ -205,10 +213,10 @@ std::vector> population_t::get_external_solutions for (auto& h_entry : queue) { // ignore CPUFJ solutions if they're not better than the best feasible. // It seems they worsen results on some instances despite the potential for improved diversity - if (h_entry.origin == solution_origin_t::CPUFJ && + if (h_entry.origin == internals::mip_solution_origin_t::CPU_FEASIBILITY_JUMP && h_entry.objective > new_best_feasible_objective) { continue; - } else if (h_entry.origin != solution_origin_t::CPUFJ && + } else if (h_entry.origin != internals::mip_solution_origin_t::CPU_FEASIBILITY_JUMP && h_entry.objective > new_best_feasible_objective) { new_best_feasible_objective = h_entry.objective; } @@ -233,7 +241,7 @@ std::vector> population_t::get_external_solutions problem_ptr->n_integer_vars); } sol.handle_ptr->sync_stream(); - return_vector.emplace_back(std::move(sol)); + return_vector.emplace_back(std::move(sol), h_entry.origin); counter++; } } @@ -253,126 +261,52 @@ std::vector> population_t::get_external_solutions template bool population_t::is_better_than_best_feasible(solution_t& sol) { - bool obj_better = sol.get_objective() < best_feasible_objective; - return obj_better && sol.get_feasible(); -} - -template -void population_t::invoke_get_solution_callback( - solution_t& sol, internals::get_solution_callback_t* callback) -{ - f_t user_objective = sol.get_user_objective(); - f_t user_bound = context.stats.get_solution_bound(); - solution_t temp_sol(sol); - problem_ptr->post_process_assignment(temp_sol.assignment); - if (context.settings.mip_scaling) { - rmm::device_uvector dummy(0, temp_sol.handle_ptr->get_stream()); - context.scaling.unscale_solutions(temp_sol.assignment, dummy); - } - if (problem_ptr->has_papilo_presolve_data()) { - problem_ptr->papilo_uncrush_assignment(temp_sol.assignment); + if (!sol.get_feasible()) { return false; } + f_t threshold = best_feasible_objective; + if ((context.settings.determinism_mode & CUOPT_DETERMINISM_BB) && + context.branch_and_bound_ptr != nullptr) { + threshold = context.branch_and_bound_ptr->get_upper_bound(); } - - std::vector user_objective_vec(1); - std::vector user_bound_vec(1); - std::vector user_assignment_vec(temp_sol.assignment.size()); - user_objective_vec[0] = user_objective; - user_bound_vec[0] = user_bound; - raft::copy(user_assignment_vec.data(), - temp_sol.assignment.data(), - temp_sol.assignment.size(), - temp_sol.handle_ptr->get_stream()); - temp_sol.handle_ptr->sync_stream(); - callback->get_solution(user_assignment_vec.data(), - user_objective_vec.data(), - user_bound_vec.data(), - callback->get_user_data()); + return sol.get_objective() < threshold; } template -void population_t::run_solution_callbacks(solution_t& sol) +void population_t::run_solution_callbacks( + solution_t& sol, internals::mip_solution_origin_t callback_origin) { - bool better_solution_found = is_better_than_best_feasible(sol); - auto user_callbacks = context.settings.get_mip_callbacks(); - if (better_solution_found) { - if (context.settings.benchmark_info_ptr != nullptr) { - context.settings.benchmark_info_ptr->last_improvement_of_best_feasible = timer.elapsed_time(); - } - CUOPT_LOG_DEBUG("Population: Found new best solution %g", sol.get_user_objective()); - if (problem_ptr->branch_and_bound_callback != nullptr) { - problem_ptr->branch_and_bound_callback(sol.get_host_assignment()); - } - for (auto callback : user_callbacks) { - if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) { - auto get_sol_callback = static_cast(callback); - invoke_get_solution_callback(sol, get_sol_callback); + if (is_better_than_best_feasible(sol)) { + const bool deterministic_bb = (context.settings.determinism_mode & CUOPT_DETERMINISM_BB) && + context.branch_and_bound_ptr != nullptr; + + if (deterministic_bb) { + const double work_timestamp = context.gpu_heur_loop.current_producer_work(); + cuopt_assert(std::isfinite(work_timestamp), + "Deterministic heuristic work timestamp must be finite"); + context.branch_and_bound_ptr->queue_external_solution_deterministic( + sol.get_host_assignment(), sol.get_user_objective(), work_timestamp, callback_origin); + } else { + const double work_timestamp = context.gpu_heur_loop.current_work(); + const auto payload = context.solution_publication.build_callback_payload( + context.problem_ptr, context.scaling, sol, callback_origin, work_timestamp); + context.solution_publication.publish_new_best_feasible(payload, timer.elapsed_time()); + + if (context.branch_and_bound_ptr != nullptr && + context.problem_ptr->branch_and_bound_callback != nullptr) { + context.problem_ptr->branch_and_bound_callback(sol.get_host_assignment(), callback_origin); } } - // save the best objective here, because we might not have been able to return the solution to - // the user because of the unscaling that causes infeasibility. - // This prevents an issue of repaired, or a fully feasible solution being reported in the call - // back in next run. + best_feasible_objective = sol.get_objective(); } - for (auto callback : user_callbacks) { - if (callback->get_type() == internals::base_solution_callback_type::SET_SOLUTION) { - auto set_sol_callback = static_cast(callback); - f_t user_bound = context.stats.get_solution_bound(); - auto callback_num_variables = problem_ptr->original_problem_ptr->get_n_variables(); - rmm::device_uvector incumbent_assignment(callback_num_variables, - sol.handle_ptr->get_stream()); - solution_t outside_sol(sol); - rmm::device_scalar d_outside_sol_objective(sol.handle_ptr->get_stream()); - auto inf = std::numeric_limits::infinity(); - d_outside_sol_objective.set_value_async(inf, sol.handle_ptr->get_stream()); - sol.handle_ptr->sync_stream(); - std::vector h_incumbent_assignment(incumbent_assignment.size()); - std::vector h_outside_sol_objective(1, inf); - std::vector h_user_bound(1, user_bound); - set_sol_callback->set_solution(h_incumbent_assignment.data(), - h_outside_sol_objective.data(), - h_user_bound.data(), - set_sol_callback->get_user_data()); - f_t outside_sol_objective = h_outside_sol_objective[0]; - // The callback might be called without setting any valid solution or objective which triggers - // asserts - if (outside_sol_objective == inf) { return; } - d_outside_sol_objective.set_value_async(outside_sol_objective, sol.handle_ptr->get_stream()); - raft::copy(incumbent_assignment.data(), - h_incumbent_assignment.data(), - incumbent_assignment.size(), - sol.handle_ptr->get_stream()); - - if (context.settings.mip_scaling) { context.scaling.scale_solutions(incumbent_assignment); } - bool is_valid = problem_ptr->pre_process_assignment(incumbent_assignment); - if (!is_valid) { return; } - cuopt_assert(outside_sol.assignment.size() == incumbent_assignment.size(), - "Incumbent assignment size mismatch"); - raft::copy(outside_sol.assignment.data(), - incumbent_assignment.data(), - incumbent_assignment.size(), - sol.handle_ptr->get_stream()); - outside_sol.compute_feasibility(); - - CUOPT_LOG_DEBUG("Injected solution feasibility = %d objective = %g excess = %g", - outside_sol.get_feasible(), - outside_sol.get_user_objective(), - outside_sol.get_total_excess()); - if (std::abs(outside_sol.get_user_objective() - outside_sol_objective) > 1e-6) { - cuopt_func_call( - CUOPT_LOG_DEBUG("External solution objective mismatch: outside_sol.get_user_objective() " - "= %g, outside_sol_objective = %g", - outside_sol.get_user_objective(), - outside_sol_objective)); - } - cuopt_assert(std::abs(outside_sol.get_user_objective() - outside_sol_objective) <= 1e-6, - "External solution objective mismatch"); - auto h_outside_sol = outside_sol.get_host_assignment(); - add_external_solution( - h_outside_sol, outside_sol.get_objective(), solution_origin_t::EXTERNAL); - } - } + context.solution_injection.invoke_set_solution_callbacks( + problem_ptr, + context.scaling, + sol, + [this]( + const std::vector& assignment, f_t objective, internals::mip_solution_origin_t origin) { + add_external_solution(assignment, objective, origin); + }); } template @@ -408,7 +342,8 @@ void population_t::adjust_weights_according_to_best_feasible() } template -std::pair population_t::add_solution(solution_t&& sol) +std::pair population_t::add_solution( + solution_t&& sol, internals::mip_solution_origin_t callback_origin) { std::lock_guard lock(write_mutex); raft::common::nvtx::range fun_scope("add_solution"); @@ -418,16 +353,18 @@ std::pair population_t::add_solution(solution_t&& // for hash computation, quality calculation, and similarity comparisons. sol.handle_ptr->sync_stream(); population_hash_map.insert(sol); - double sol_cost = sol.get_quality(weights); - bool best_updated = false; - CUOPT_LOG_DEBUG("Adding solution with quality %f and objective %f n_integers %d!", + double sol_cost = sol.get_quality(weights); + bool best_updated = false; + const uint32_t candidate_hash = sol.get_hash(); + CUOPT_LOG_DEBUG("Adding solution with quality %f and objective %f n_integers %d, hash %x!", sol_cost, sol.get_user_objective(), - sol.n_assigned_integers); + sol.n_assigned_integers, + candidate_hash); // We store the best feasible found so far at index 0. if (sol.get_feasible() && (solutions[0].first == false || sol_cost + OBJECTIVE_EPSILON < indices[0].second)) { - run_solution_callbacks(sol); + run_solution_callbacks(sol, callback_origin); solutions[0].first = true; // we only have move assignment operator solution_t temp_sol(sol); @@ -713,7 +650,7 @@ void population_t::halve_the_population() clear_except_best_feasible(); var_threshold = std::max(var_threshold * 0.97, 0.5 * problem_ptr->n_integer_vars); for (auto& sol : sol_vec) { - add_solution(solution_t(sol)); + add_solution(solution_t(sol), internals::mip_solution_origin_t::LOCAL_SEARCH); } if (counter++ > max_adjustments) break; } @@ -725,7 +662,7 @@ void population_t::halve_the_population() max_var_threshold, std::min((size_t)(var_threshold * 1.02), (size_t)(0.995 * problem_ptr->n_integer_vars))); for (auto& sol : sol_vec) { - add_solution(solution_t(sol)); + add_solution(solution_t(sol), internals::mip_solution_origin_t::LOCAL_SEARCH); } if (counter++ > max_adjustments) break; } @@ -751,7 +688,7 @@ void population_t::start_threshold_adjustment() } template -void population_t::adjust_threshold(cuopt::timer_t timer) +void population_t::adjust_threshold(cuopt::work_limit_timer_t timer) { double time_ratio = (timer.elapsed_time() - population_start_time) / (timer.get_time_limit() - population_start_time); @@ -840,23 +777,29 @@ bool population_t::test_invariant() template void population_t::print() { + std::vector hashes; + for (auto& index : indices) + hashes.push_back(solutions[index.first].second.get_hash()); + uint32_t final_hash = compute_hash(hashes); CUOPT_LOG_DEBUG(" -------------- "); - CUOPT_LOG_DEBUG("%s infeas weight %f threshold %d/%d:", + CUOPT_LOG_DEBUG("%s infeas weight %f threshold %d/%d (hash %x):", name.c_str(), infeasibility_importance, var_threshold, - problem_ptr->n_integer_vars); + problem_ptr->n_integer_vars, + final_hash); i_t i = 0; for (auto& index : indices) { if (index.first == 0 && solutions[0].first) { CUOPT_LOG_DEBUG(" Best feasible: %f", solutions[index.first].second.get_user_objective()); } - CUOPT_LOG_DEBUG("%d : %f\t%f\t%f\t%d", + CUOPT_LOG_DEBUG("%d : %f\t%f\t%f\t%d (hash %x)", i, index.second, solutions[index.first].second.get_total_excess(), solutions[index.first].second.get_user_objective(), - solutions[index.first].second.get_feasible()); + solutions[index.first].second.get_feasible(), + solutions[index.first].second.get_hash()); i++; } CUOPT_LOG_DEBUG(" -------------- "); @@ -865,8 +808,8 @@ void population_t::print() template void population_t::run_all_recombiners(solution_t& sol) { - std::vector> sol_vec; - sol_vec.emplace_back(std::move(solution_t(sol))); + std::vector::drained_external_solution_t> sol_vec; + sol_vec.emplace_back(solution_t(sol), internals::mip_solution_origin_t::LOCAL_SEARCH); dm.recombine_and_ls_with_all(sol_vec, true); } diff --git a/cpp/src/mip_heuristics/diversity/population.cuh b/cpp/src/mip_heuristics/diversity/population.cuh index 2509ae17df..5968f56a0f 100644 --- a/cpp/src/mip_heuristics/diversity/population.cuh +++ b/cpp/src/mip_heuristics/diversity/population.cuh @@ -25,22 +25,20 @@ namespace cuopt::linear_programming::detail { template class diversity_manager_t; -enum class solution_origin_t { BRANCH_AND_BOUND, CPUFJ, RINS, EXTERNAL }; - -constexpr const char* solution_origin_to_string(solution_origin_t origin) -{ - switch (origin) { - case solution_origin_t::BRANCH_AND_BOUND: return "B&B"; - case solution_origin_t::CPUFJ: return "CPUFJ"; - case solution_origin_t::RINS: return "RINS"; - case solution_origin_t::EXTERNAL: return "injected"; - default: return "unknown"; - } -} - template class population_t { public: + struct drained_external_solution_t { + drained_external_solution_t(solution_t&& solution_, + internals::mip_solution_origin_t origin_) + : solution(std::move(solution_)), origin(origin_) + { + } + + solution_t solution; + internals::mip_solution_origin_t origin; + }; + population_t(std::string const& name, mip_solver_context_t& context, diversity_manager_t& dm, @@ -83,6 +81,7 @@ class population_t { a.first = false; indices[0].second = std::numeric_limits::max(); indices.erase(indices.begin() + 1, indices.end()); + best_feasible_objective = std::numeric_limits::max(); } void clear_except_best_feasible() @@ -92,6 +91,7 @@ class population_t { } solutions[indices[0].first].first = true; indices.erase(indices.begin() + 1, indices.end()); + best_feasible_objective = solutions[indices[0].first].second.get_objective(); } // ------------------- @@ -103,16 +103,18 @@ class population_t { /*! \brief { Add a solution to population. Similar solutions may be ejected from the pool. } * \return { -1 = not inserted , others = inserted index} */ - std::pair add_solution(solution_t&& sol); + std::pair add_solution(solution_t&& sol, + internals::mip_solution_origin_t callback_origin); void add_external_solution(const std::vector& solution, f_t objective, - solution_origin_t origin); - std::vector> get_external_solutions(); + internals::mip_solution_origin_t origin); + std::vector get_external_solutions(); void add_external_solutions_to_population(); size_t get_external_solution_size(); void preempt_heuristic_solver(); - void add_solutions_from_vec(std::vector>&& solutions); + void add_solutions_from_vec(std::vector>&& solutions, + internals::mip_solution_origin_t callback_origin); // Updates the cstr weights according to the best solutions feasibility void compute_new_weights(); @@ -122,7 +124,7 @@ class population_t { // updates qualities of each solution void update_qualities(); // adjusts the threshold of the population - void adjust_threshold(cuopt::timer_t timer); + void adjust_threshold(cuopt::work_limit_timer_t timer); /*! \param sol { Input solution } * \return { Index of the best solution similar to sol. If no similar is found we return * max_solutions. }*/ @@ -153,7 +155,8 @@ class population_t { std::vector> population_to_vector(); void halve_the_population(); - void run_solution_callbacks(solution_t& sol); + void run_solution_callbacks(solution_t& sol, + internals::mip_solution_origin_t callback_origin); void adjust_weights_according_to_best_feasible(); @@ -161,9 +164,6 @@ class population_t { void diversity_step(i_t max_iterations_without_improvement); - void invoke_get_solution_callback(solution_t& sol, - internals::get_solution_callback_t* callback); - // does some consistency tests bool test_invariant(); @@ -186,7 +186,9 @@ class population_t { struct external_solution_t { external_solution_t() = default; - external_solution_t(const std::vector& solution, f_t objective, solution_origin_t origin) + external_solution_t(const std::vector& solution, + f_t objective, + internals::mip_solution_origin_t origin) : solution(solution), objective(objective), origin(origin), @@ -195,7 +197,7 @@ class population_t { } std::vector solution; f_t objective; - solution_origin_t origin; + internals::mip_solution_origin_t origin; timer_t timer; // debug timer to track how long a solution has lingered in the queue }; @@ -209,7 +211,7 @@ class population_t { std::atomic solutions_in_external_queue_ = false; f_t best_feasible_objective = std::numeric_limits::max(); assignment_hash_map_t population_hash_map; - cuopt::timer_t timer; + cuopt::work_limit_timer_t timer; }; } // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip_heuristics/diversity/recombiners/bound_prop_recombiner.cuh b/cpp/src/mip_heuristics/diversity/recombiners/bound_prop_recombiner.cuh index 9d6bb3902c..80f8383fab 100644 --- a/cpp/src/mip_heuristics/diversity/recombiners/bound_prop_recombiner.cuh +++ b/cpp/src/mip_heuristics/diversity/recombiners/bound_prop_recombiner.cuh @@ -29,6 +29,7 @@ class bound_prop_recombiner_t : public recombiner_t { rng(cuopt::seed_generator::get_seed()), vars_to_fix(n_vars, handle_ptr->get_stream()) { + thrust::fill(handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end(), -1); } void get_probing_values_for_infeasible( @@ -131,9 +132,9 @@ class bound_prop_recombiner_t : public recombiner_t { }); } - std::pair, bool> recombine(solution_t& a, - solution_t& b, - const weight_t& weights) + std::tuple, bool, double> recombine(solution_t& a, + solution_t& b, + const weight_t& weights) { raft::common::nvtx::range fun_scope("bound_prop_recombiner"); auto& guiding_solution = a.get_feasible() ? a : b; @@ -148,10 +149,11 @@ class bound_prop_recombiner_t : public recombiner_t { i_t n_vars_from_other = n_different_vars; i_t fixed_from_guiding = 0; i_t fixed_from_other = 0; + i_t seed = cuopt::seed_generator::get_seed(); if (n_different_vars > (i_t)bp_recombiner_config_t::max_n_of_vars_from_other) { fixed_from_guiding = n_vars_from_other - bp_recombiner_config_t::max_n_of_vars_from_other; n_vars_from_other = bp_recombiner_config_t::max_n_of_vars_from_other; - thrust::default_random_engine g{(unsigned int)cuopt::seed_generator::get_seed()}; + thrust::default_random_engine g{(unsigned int)seed}; thrust::shuffle(a.handle_ptr->get_thrust_policy(), this->remaining_indices.data(), this->remaining_indices.data() + n_different_vars, @@ -160,12 +162,35 @@ class bound_prop_recombiner_t : public recombiner_t { i_t n_vars_from_guiding = a.problem_ptr->n_integer_vars - n_vars_from_other; CUOPT_LOG_DEBUG( "n_vars_from_guiding %d n_vars_from_other %d", n_vars_from_guiding, n_vars_from_other); + + // DETERMINISM DEBUG: Log everything that could affect divergence + CUOPT_DETERMINISM_LOG("BP_DET: sol_a_hash=0x%x sol_b_hash=0x%x offspring_hash=0x%x, seed %x", + a.get_hash(), + b.get_hash(), + offspring.get_hash(), + seed); + CUOPT_DETERMINISM_LOG("BP_DET: n_different_vars=%d n_vars_from_other=%d n_vars_from_guiding=%d", + n_different_vars, + n_vars_from_other, + n_vars_from_guiding); + CUOPT_DETERMINISM_LOG( + "BP_DET: remaining_indices_hash=0x%x (first %d elements)", + detail::compute_hash(make_span(this->remaining_indices), a.handle_ptr->get_stream()), + std::min((i_t)10, n_vars_from_other)); + CUOPT_DETERMINISM_LOG("BP_DET: guiding_feasible=%d other_feasible=%d expensive_to_fix=%d", + guiding_solution.get_feasible(), + other_solution.get_feasible(), + a.problem_ptr->expensive_to_fix_vars); + CUOPT_DETERMINISM_LOG( + "BP_DET: fixed_from_guiding=%d fixed_from_other=%d", fixed_from_guiding, fixed_from_other); + // if either all integers are from A(meaning all are common) or all integers are from B(meaning // all are different), return if (n_vars_from_guiding == 0 || n_vars_from_other == 0) { CUOPT_LOG_DEBUG("Returning false because all vars are common or different"); - return std::make_pair(offspring, false); + return std::make_tuple(offspring, false, 0.0); } + double work = static_cast(n_vars_from_other); cuopt_assert(a.problem_ptr == b.problem_ptr, "The two solutions should not refer to different problems"); @@ -175,9 +200,16 @@ class bound_prop_recombiner_t : public recombiner_t { a.handle_ptr->get_stream()); probing_config_t probing_config(a.problem_ptr->n_variables, a.handle_ptr); if (guiding_solution.get_feasible() && !a.problem_ptr->expensive_to_fix_vars) { + CUOPT_DETERMINISM_LOG("BP_DET: Taking FEASIBLE path (with variable fixing)"); this->compute_vars_to_fix(offspring, vars_to_fix, n_vars_from_other, n_vars_from_guiding); + CUOPT_DETERMINISM_LOG("BP_DET: vars_to_fix_size=%lu", vars_to_fix.size()); auto [fixed_problem, fixed_assignment, variable_map] = offspring.fix_variables(vars_to_fix); - timer_t timer(bp_recombiner_config_t::bounds_prop_time_limit); + CUOPT_DETERMINISM_LOG("BP_DET: fixed_problem_fingerprint=0x%x variable_map_size=%lu", + fixed_problem.get_fingerprint(), + variable_map.size()); + work_limit_timer_t timer(this->context.gpu_heur_loop, + bp_recombiner_config_t::bounds_prop_time_limit, + *this->context.termination); rmm::device_uvector old_assignment(offspring.assignment, offspring.handle_ptr->get_stream()); offspring.handle_ptr->sync_stream(); @@ -197,26 +229,44 @@ class bound_prop_recombiner_t : public recombiner_t { constraint_prop.single_rounding_only = true; constraint_prop.apply_round(offspring, lp_run_time_after_feasible, timer, probing_config); constraint_prop.single_rounding_only = false; - cuopt_func_call(bool feasible_after_bounds_prop = offspring.get_feasible()); + offspring.compute_feasibility(); + bool feasible_after_bounds_prop = offspring.get_feasible(); offspring.handle_ptr->sync_stream(); offspring.problem_ptr = a.problem_ptr; fixed_assignment = std::move(offspring.assignment); offspring.assignment = std::move(old_assignment); offspring.handle_ptr->sync_stream(); offspring.unfix_variables(fixed_assignment, variable_map); - cuopt_func_call(bool feasible_after_unfix = offspring.get_feasible()); - // May be triggered due to numerical issues - // TODO: investigate further - // cuopt_assert(feasible_after_unfix == feasible_after_bounds_prop, - // "Feasible after unfix should be same as feasible after bounds prop!"); + offspring.compute_feasibility(); + bool feasible_after_unfix = offspring.get_feasible(); + cuopt_func_call(f_t excess_after_unfix = offspring.get_total_excess()); + if (feasible_after_unfix != feasible_after_bounds_prop) { + CUOPT_LOG_WARN("Numerical issue in bounds prop, infeasibility after unfix"); + // might become infeasible after unfixing due to numerical issues. Check that the excess + // remains consistent + // CUOPT_LOG_ERROR("Excess: %g, %g, %g, %g, feas %d", offspring.get_total_excess(), + // offspring.compute_max_constraint_violation(), offspring.compute_max_int_violation(), + // offspring.compute_max_variable_violation(), feasible_after_unfix); + // cuopt_assert(fabs(excess_after_unfix - excess_before) < 1e-6, + // "Excess after unfix should be same as before unfix!"); + } a.handle_ptr->sync_stream(); } else { - timer_t timer(bp_recombiner_config_t::bounds_prop_time_limit); + CUOPT_DETERMINISM_LOG("BP_DET: Taking INFEASIBLE path (no variable fixing)"); + work_limit_timer_t timer(this->context.gpu_heur_loop, + bp_recombiner_config_t::bounds_prop_time_limit, + *this->context.termination); get_probing_values_for_infeasible( guiding_solution, other_solution, offspring, probing_values, n_vars_from_other); probing_config.probing_values = host_copy(probing_values, offspring.handle_ptr->get_stream()); + CUOPT_DETERMINISM_LOG( + "BP_DET: probing_values_hash=0x%x", + detail::compute_hash(make_span(probing_values), a.handle_ptr->get_stream())); constraint_prop.apply_round(offspring, lp_run_time_after_feasible, timer, probing_config); } + CUOPT_DETERMINISM_LOG("BP_DET: After apply_round: offspring_hash=0x%x feasible=%d", + offspring.get_hash(), + offspring.get_feasible()); constraint_prop.max_n_failed_repair_iterations = 1; cuopt_func_call(offspring.test_number_all_integer()); bool better_cost_than_parents = @@ -236,11 +286,17 @@ class bound_prop_recombiner_t : public recombiner_t { bp_recombiner_config_t::decrease_max_n_of_vars_from_other(); } } + CUOPT_DETERMINISM_LOG( + "BP_DET: Final offspring_hash=0x%x same_as_parents=%d better_cost=%d better_feas=%d", + offspring.get_hash(), + same_as_parents, + better_cost_than_parents, + better_feasibility_than_parents); if (better_cost_than_parents || better_feasibility_than_parents) { CUOPT_LOG_DEBUG("Offspring is feasible or better than both parents"); - return std::make_pair(offspring, true); + return std::make_tuple(offspring, true, work); } - return std::make_pair(offspring, !same_as_parents); + return std::make_tuple(offspring, !same_as_parents, work); } rmm::device_uvector vars_to_fix; diff --git a/cpp/src/mip_heuristics/diversity/recombiners/fp_recombiner.cuh b/cpp/src/mip_heuristics/diversity/recombiners/fp_recombiner.cuh index 1cca1ba371..786a3e8798 100644 --- a/cpp/src/mip_heuristics/diversity/recombiners/fp_recombiner.cuh +++ b/cpp/src/mip_heuristics/diversity/recombiners/fp_recombiner.cuh @@ -35,9 +35,9 @@ class fp_recombiner_t : public recombiner_t { { } - std::pair, bool> recombine(solution_t& a, - solution_t& b, - const weight_t& weights) + std::tuple, bool, double> recombine(solution_t& a, + solution_t& b, + const weight_t& weights) { raft::common::nvtx::range fun_scope("FP recombiner"); auto& guiding_solution = a.get_feasible() ? a : b; @@ -50,6 +50,7 @@ class fp_recombiner_t : public recombiner_t { CUOPT_LOG_DEBUG("FP rec: Number of different variables %d MAX_VARS %d", n_different_vars, fp_recombiner_config_t::max_n_of_vars_from_other); + CUOPT_DETERMINISM_LOG("FP rec: offspring hash 0x%x", offspring.get_hash()); i_t n_vars_from_other = n_different_vars; if (n_vars_from_other > (i_t)fp_recombiner_config_t::max_n_of_vars_from_other) { n_vars_from_other = fp_recombiner_config_t::max_n_of_vars_from_other; @@ -62,17 +63,39 @@ class fp_recombiner_t : public recombiner_t { i_t n_vars_from_guiding = a.problem_ptr->n_integer_vars - n_vars_from_other; if (n_vars_from_other == 0 || n_vars_from_guiding == 0) { CUOPT_LOG_DEBUG("Returning false because all vars are common or different"); - return std::make_pair(offspring, false); + return std::make_tuple(offspring, false, 0.0); } + // TODO: CHANGE + double work = static_cast(n_vars_from_other); CUOPT_LOG_DEBUG( "n_vars_from_guiding %d n_vars_from_other %d", n_vars_from_guiding, n_vars_from_other); + CUOPT_DETERMINISM_LOG( + "FP rec: offspring hash 0x%x, vars to fix 0x%x", + offspring.get_hash(), + detail::compute_hash(make_span(vars_to_fix), offspring.handle_ptr->get_stream())); this->compute_vars_to_fix(offspring, vars_to_fix, n_vars_from_other, n_vars_from_guiding); + CUOPT_DETERMINISM_LOG( + "FP rec post computevarstofix: offspring hash 0x%x, vars to fix 0x%x", + offspring.get_hash(), + detail::compute_hash(make_span(vars_to_fix), offspring.handle_ptr->get_stream())); auto [fixed_problem, fixed_assignment, variable_map] = offspring.fix_variables(vars_to_fix); + CUOPT_DETERMINISM_LOG( + "FP rec: fixed_problem hash 0x%x assigned hash 0x%x", + fixed_problem.get_fingerprint(), + detail::compute_hash(make_span(fixed_assignment), offspring.handle_ptr->get_stream())); fixed_problem.check_problem_representation(true); if (!guiding_solution.get_feasible() && !other_solution.get_feasible()) { + CUOPT_DETERMINISM_LOG("FP rec: running LP with infeasibility detection"); relaxed_lp_settings_t lp_settings; lp_settings.time_limit = fp_recombiner_config_t::infeasibility_detection_time_limit; - lp_settings.tolerance = fixed_problem.tolerances.absolute_tolerance; + if (this->context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS) { + lp_settings.time_limit = + std::numeric_limits::max(); // TODO should be global time limit + lp_settings.work_limit = fp_recombiner_config_t::infeasibility_detection_time_limit; + lp_settings.work_context = &this->context.gpu_heur_loop; + cuopt_assert(lp_settings.work_context != nullptr, "Missing deterministic work context"); + } + lp_settings.tolerance = fixed_problem.tolerances.absolute_tolerance; lp_settings.return_first_feasible = true; lp_settings.save_state = true; lp_settings.check_infeasibility = true; @@ -83,7 +106,7 @@ class fp_recombiner_t : public recombiner_t { lp_response.get_termination_status() == pdlp_termination_status_t::DualInfeasible || lp_response.get_termination_status() == pdlp_termination_status_t::TimeLimit) { CUOPT_LOG_DEBUG("FP recombiner failed because LP found infeasible!"); - return std::make_pair(offspring, false); + return std::make_tuple(offspring, false, 0.0); } } // brute force rounding threshold is 8 @@ -96,7 +119,16 @@ class fp_recombiner_t : public recombiner_t { offspring.handle_ptr->sync_stream(); offspring.assignment = std::move(fixed_assignment); cuopt_func_call(offspring.test_variable_bounds(false)); - timer_t timer(fp_recombiner_config_t::fp_time_limit); + CUOPT_DETERMINISM_LOG( + "FP rec pre-descent: offspring_hash=0x%x fixed_assignment_hash=0x%x " + "problem_fingerprint=0x%x fixed_n_integer_vars=%d", + offspring.get_hash(), + detail::compute_hash(offspring.assignment, offspring.handle_ptr->get_stream()), + fixed_problem.get_fingerprint(), + fixed_problem.n_integer_vars); + work_limit_timer_t timer(this->context.gpu_heur_loop, + fp_recombiner_config_t::fp_time_limit, + *this->context.termination); fp.timer = timer; fp.cycle_queue.reset(offspring); fp.reset(); @@ -134,9 +166,9 @@ class fp_recombiner_t : public recombiner_t { !guiding_solution.get_feasible(); if (better_cost_than_parents || better_feasibility_than_parents) { CUOPT_LOG_DEBUG("Offspring is feasible or better than both parents"); - return std::make_pair(offspring, true); + return std::make_tuple(offspring, true, work); } - return std::make_pair(offspring, !same_as_parents); + return std::make_tuple(offspring, !same_as_parents, work); } rmm::device_uvector vars_to_fix; // keep a copy of FP to prevent interference with generation FP diff --git a/cpp/src/mip_heuristics/diversity/recombiners/line_segment_recombiner.cuh b/cpp/src/mip_heuristics/diversity/recombiners/line_segment_recombiner.cuh index d413af86cd..ab0ba3d21c 100644 --- a/cpp/src/mip_heuristics/diversity/recombiners/line_segment_recombiner.cuh +++ b/cpp/src/mip_heuristics/diversity/recombiners/line_segment_recombiner.cuh @@ -66,22 +66,26 @@ class line_segment_recombiner_t : public recombiner_t { return delta_vector; } - std::pair, bool> recombine(solution_t& a, - solution_t& b, - const weight_t& weights) + std::tuple, bool, double> recombine(solution_t& a, + solution_t& b, + const weight_t& weights) { raft::common::nvtx::range fun_scope("line_segment_recombiner"); + CUOPT_DETERMINISM_LOG("LS rec: a %d b %d", a.get_hash(), b.get_hash()); auto& guiding_solution = a.get_feasible() ? a : b; auto& other_solution = a.get_feasible() ? b : a; // copy the solution from A solution_t offspring(guiding_solution); - timer_t line_segment_timer{ls_recombiner_config_t::time_limit}; + work_limit_timer_t line_segment_timer{ + this->context.gpu_heur_loop, ls_recombiner_config_t::time_limit, *this->context.termination}; // TODO after we have the conic combination, detect the lambda change // (i.e. the integral variables flip on line segment) i_t n_points_to_search = ls_recombiner_config_t::n_points_to_search; const bool is_feasibility_run = false; i_t n_different_vars = this->assign_same_integer_values(guiding_solution, other_solution, offspring); + // TODO: CHANGE + double work = static_cast(n_different_vars); rmm::device_uvector delta_vector = generate_delta_vector( guiding_solution, other_solution, offspring, n_points_to_search, n_different_vars); line_segment_search.fj.copy_weights(weights, offspring.handle_ptr); @@ -117,9 +121,9 @@ class line_segment_recombiner_t : public recombiner_t { } if (better_cost_than_parents || better_feasibility_than_parents) { CUOPT_LOG_DEBUG("Offspring is feasible or better than both parents"); - return std::make_pair(offspring, true); + return std::make_tuple(offspring, true, work); } - return std::make_pair(offspring, !same_as_parents); + return std::make_tuple(offspring, !same_as_parents, work); } line_segment_search_t& line_segment_search; diff --git a/cpp/src/mip_heuristics/diversity/recombiners/recombiner.cuh b/cpp/src/mip_heuristics/diversity/recombiners/recombiner.cuh index 89a5e86c17..e43a1d1efd 100644 --- a/cpp/src/mip_heuristics/diversity/recombiners/recombiner.cuh +++ b/cpp/src/mip_heuristics/diversity/recombiners/recombiner.cuh @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -63,6 +64,18 @@ __global__ void assign_same_variables_kernel(typename solution_t::view template class recombiner_t { public: + static const char* recombiner_name(recombiner_enum_t recombiner) + { + switch (recombiner) { + case recombiner_enum_t::BOUND_PROP: return "BOUND_PROP"; + case recombiner_enum_t::FP: return "FP"; + case recombiner_enum_t::LINE_SEGMENT: return "LINE_SEGMENT"; + case recombiner_enum_t::SUB_MIP: return "SUB_MIP"; + case recombiner_enum_t::SIZE: return "SIZE"; + } + return "UNKNOWN"; + } + recombiner_t(mip_solver_context_t& context_, i_t n_integer_vars, const raft::handle_t* handle_ptr) @@ -92,6 +105,15 @@ class recombiner_t { cuopt::make_span(remaining_indices), n_remaining.data()); i_t remaining_variables = this->n_remaining.value(a.handle_ptr->get_stream()); + // Sort the indices to resolve nondeterministic order due to atomicAdd + thrust::sort(a.handle_ptr->get_thrust_policy(), + this->remaining_indices.data(), + this->remaining_indices.data() + remaining_variables); + + CUOPT_DETERMINISM_LOG( + "remaining indices hash 0x%x, size %d", + detail::compute_hash(make_span(this->remaining_indices), a.handle_ptr->get_stream()), + remaining_variables); auto vec_remaining_indices = host_copy(this->remaining_indices.data(), remaining_variables, a.handle_ptr->get_stream()); @@ -173,6 +195,12 @@ class recombiner_t { i_t n_vars_from_guiding) { vars_to_fix.resize(n_vars_from_guiding, offspring.handle_ptr->get_stream()); + CUOPT_DETERMINISM_LOG( + "remaining indices hash 0x%x", + detail::compute_hash(make_span(this->remaining_indices), offspring.handle_ptr->get_stream())); + CUOPT_DETERMINISM_LOG("integer_indices hash 0x%x", + detail::compute_hash(make_span(offspring.problem_ptr->integer_indices), + offspring.handle_ptr->get_stream())); // set difference needs two sorted arrays thrust::sort(offspring.handle_ptr->get_thrust_policy(), this->remaining_indices.data(), @@ -195,23 +223,53 @@ class recombiner_t { "vars_to_fix should be sorted!"); } - static void init_enabled_recombiners(const problem_t& problem) + static void init_enabled_recombiners(mip_solver_context_t& context, + const problem_t& problem) { std::unordered_set enabled_recombiners; + const bool disable_fp_and_submip_for_expensive_fix = problem.expensive_to_fix_vars; + const i_t n_continuous_vars = problem.n_variables - problem.n_integer_vars; + const bool disable_submip_for_continuous_limit = + n_continuous_vars > (i_t)sub_mip_recombiner_config_t::max_continuous_vars; + const bool disable_submip_for_determinism = + (context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS) != 0; for (auto recombiner : recombiner_types) { enabled_recombiners.insert(recombiner); } - if (problem.expensive_to_fix_vars) { + if (disable_fp_and_submip_for_expensive_fix) { enabled_recombiners.erase(recombiner_enum_t::FP); enabled_recombiners.erase(recombiner_enum_t::SUB_MIP); } // check the size of the continous vars - if (problem.n_variables - problem.n_integer_vars > - (i_t)sub_mip_recombiner_config_t::max_continuous_vars) { + if (disable_submip_for_continuous_limit) { + enabled_recombiners.erase(recombiner_enum_t::SUB_MIP); + } + // submip not supported in deterministic mode yet + if (disable_submip_for_determinism) { + // temp, added for debugging enabled_recombiners.erase(recombiner_enum_t::SUB_MIP); } recombiner_t::enabled_recombiners = std::vector(enabled_recombiners.begin(), enabled_recombiners.end()); + cuopt_assert(!recombiner_t::enabled_recombiners.empty(), "No recombiners enabled after init"); + std::string order_str; + for (size_t i = 0; i < recombiner_t::enabled_recombiners.size(); ++i) { + if (i > 0) { order_str += ','; } + order_str += recombiner_name(recombiner_t::enabled_recombiners[i]); + } + CUOPT_DETERMINISM_LOG( + "Deterministic recombiner init: expensive_to_fix=%d n_continuous=%d " + "max_continuous=%zu disable_fp_submip_expensive=%d " + "disable_submip_continuous=%d disable_submip_deterministic=%d size=%zu " + "order=[%s]", + (int)problem.expensive_to_fix_vars, + (int)n_continuous_vars, + sub_mip_recombiner_config_t::max_continuous_vars, + (int)disable_fp_and_submip_for_expensive_fix, + (int)disable_submip_for_continuous_limit, + (int)disable_submip_for_determinism, + recombiner_t::enabled_recombiners.size(), + order_str.c_str()); } mip_solver_context_t& context; diff --git a/cpp/src/mip_heuristics/diversity/recombiners/recombiner_stats.hpp b/cpp/src/mip_heuristics/diversity/recombiners/recombiner_stats.hpp index 044e313284..9b0c4c8d69 100644 --- a/cpp/src/mip_heuristics/diversity/recombiners/recombiner_stats.hpp +++ b/cpp/src/mip_heuristics/diversity/recombiners/recombiner_stats.hpp @@ -77,6 +77,11 @@ struct all_recombine_stats { std::optional last_attempt; double last_recombiner_time; std::chrono::high_resolution_clock::time_point last_recombiner_start_time; + double last_recombiner_work; + + void set_recombiner_work(double work) { last_recombiner_work = work; } + + double get_last_recombiner_work() { return last_recombiner_work; } void start_recombiner_time() { diff --git a/cpp/src/mip_heuristics/diversity/recombiners/sub_mip.cuh b/cpp/src/mip_heuristics/diversity/recombiners/sub_mip.cuh index a867141d0a..1b4e3f562b 100644 --- a/cpp/src/mip_heuristics/diversity/recombiners/sub_mip.cuh +++ b/cpp/src/mip_heuristics/diversity/recombiners/sub_mip.cuh @@ -37,9 +37,9 @@ class sub_mip_recombiner_t : public recombiner_t { solution_vector.push_back(solution); } - std::pair, bool> recombine(solution_t& a, - solution_t& b, - const weight_t& weights) + std::tuple, bool, double> recombine(solution_t& a, + solution_t& b, + const weight_t& weights) { raft::common::nvtx::range fun_scope("Sub-MIP recombiner"); solution_vector.clear(); @@ -65,8 +65,10 @@ class sub_mip_recombiner_t : public recombiner_t { i_t n_vars_from_guiding = a.problem_ptr->n_integer_vars - n_vars_from_other; if (n_vars_from_other == 0 || n_vars_from_guiding == 0) { CUOPT_LOG_DEBUG("Returning false because all vars are common or different"); - return std::make_pair(offspring, false); + return std::make_tuple(offspring, false, 0.0); } + // TODO: CHANGE + double work = static_cast(n_vars_from_other); CUOPT_LOG_DEBUG( "n_vars_from_guiding %d n_vars_from_other %d", n_vars_from_guiding, n_vars_from_other); this->compute_vars_to_fix(offspring, vars_to_fix, n_vars_from_other, n_vars_from_guiding); @@ -110,10 +112,11 @@ class sub_mip_recombiner_t : public recombiner_t { branch_and_bound_settings.max_cut_passes = 0; branch_and_bound_settings.clique_cuts = 0; branch_and_bound_settings.sub_mip = 1; - branch_and_bound_settings.solution_callback = [this](std::vector& solution, - f_t objective) { - this->solution_callback(solution, objective); - }; + branch_and_bound_settings.new_incumbent_callback = + [this](std::vector& solution, + f_t objective, + const cuopt::internals::mip_solution_callback_info_t&, + double) { this->solution_callback(solution, objective); }; // disable B&B logs, so that it is not interfering with the main B&B thread branch_and_bound_settings.log.log = false; @@ -181,7 +184,7 @@ class sub_mip_recombiner_t : public recombiner_t { sol.clamp_within_bounds(); // Scaling might bring some very slight variable bound violations sol.compute_feasibility(); cuopt_func_call(sol.test_variable_bounds()); - population.add_solution(std::move(sol)); + population.add_solution(std::move(sol), internals::mip_solution_origin_t::SUB_MIP); } bool better_cost_than_parents = offspring.get_quality(weights) < @@ -191,9 +194,9 @@ class sub_mip_recombiner_t : public recombiner_t { !guiding_solution.get_feasible(); if (better_cost_than_parents || better_feasibility_than_parents) { CUOPT_LOG_DEBUG("Offspring is feasible or better than both parents"); - return std::make_pair(offspring, true); + return std::make_tuple(offspring, true, work); } - return std::make_pair(offspring, !std::isnan(branch_and_bound_solution.objective)); + return std::make_tuple(offspring, !std::isnan(branch_and_bound_solution.objective), work); } rmm::device_uvector vars_to_fix; mip_solver_context_t& context; diff --git a/cpp/src/mip_heuristics/diversity/weights.cuh b/cpp/src/mip_heuristics/diversity/weights.cuh index 7502ae9210..fbe72aba8e 100644 --- a/cpp/src/mip_heuristics/diversity/weights.cuh +++ b/cpp/src/mip_heuristics/diversity/weights.cuh @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -12,6 +12,8 @@ #include #include +#include + namespace cuopt::linear_programming::detail { template @@ -25,6 +27,11 @@ struct weight_t { objective_weight.set_value_async(one, handle_ptr->get_stream()); } + uint32_t get_hash(rmm::cuda_stream_view stream = rmm::cuda_stream_default) const + { + return compute_hash(cstr_weights, stream) ^ compute_hash(objective_weight.value(stream)); + } + rmm::device_uvector cstr_weights; rmm::device_scalar objective_weight; }; diff --git a/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump.cu b/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump.cu index e9cf0760de..33ff207260 100644 --- a/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump.cu +++ b/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump.cu @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,9 @@ #include +#include +#include + #define FJ_LOG_PREFIX "FJ: " namespace cuopt::linear_programming::detail { @@ -432,9 +436,11 @@ void fj_t::climber_init(i_t climber_idx, const rmm::cuda_stream_view& f_t inf = std::numeric_limits::infinity(); climber->best_objective.set_value_async(inf, climber_stream); climber->saved_solution_objective.set_value_async(inf, climber_stream); - climber->violation_score.set_value_to_zero_async(climber_stream); - climber->weighted_violation_score.set_value_to_zero_async(climber_stream); - init_lhs_and_violation<<<256, 256, 0, climber_stream.value()>>>(view); + refresh_lhs_and_violation(climber_stream); + + // printf("init: Violated constraints hash: %x\n", compute_hash( + // make_span(climber->violated_constraints.contents, 0, + // climber->violated_constraints.set_size.value(climber_stream)), climber_stream)); // initialize the best_objective values according to the initial assignment f_t best_obj = compute_objective_from_vec( @@ -650,10 +656,10 @@ void fj_t::run_step_device(const rmm::cuda_stream_view& climber_stream auto [grid_setval, blocks_setval] = setval_launch_dims; auto [grid_update_changed_constraints, blocks_update_changed_constraints] = update_changed_constraints_launch_dims; - auto [grid_resetmoves, blocks_resetmoves] = resetmoves_launch_dims; - auto [grid_resetmoves_bin, blocks_resetmoves_bin] = resetmoves_bin_launch_dims; - auto [grid_update_weights, blocks_update_weights] = update_weights_launch_dims; - auto [grid_lift_move, blocks_lift_move] = lift_move_launch_dims; + auto [grid_resetmoves, blocks_resetmoves] = resetmoves_launch_dims; + auto [grid_resetmoves_bin, blocks_resetmoves_bin] = resetmoves_bin_launch_dims; + [[maybe_unused]] auto [grid_update_weights, blocks_update_weights] = update_weights_launch_dims; + [[maybe_unused]] auto [grid_lift_move, blocks_lift_move] = lift_move_launch_dims; auto& data = *climbers[climber_idx]; auto v = data.view(); @@ -841,16 +847,228 @@ void fj_t::refresh_lhs_and_violation(const rmm::cuda_stream_view& stre auto v = data.view(); data.violated_constraints.clear(stream); - data.violation_score.set_value_to_zero_async(stream); - data.weighted_violation_score.set_value_to_zero_async(stream); init_lhs_and_violation<<<4096, 256, 0, stream>>>(v); + // both transformreduce could be fused; but oh well hardly a bottleneck + auto violation = + thrust::transform_reduce(rmm::exec_policy(stream), + thrust::make_counting_iterator(0), + thrust::make_counting_iterator(pb_ptr->n_constraints), + cuda::proclaim_return_type([v] __device__(i_t cstr_idx) { + return v.excess_score(cstr_idx, v.incumbent_lhs[cstr_idx]); + }), + (f_t)0, + thrust::plus()); + auto weighted_violation = thrust::transform_reduce( + rmm::exec_policy(stream), + thrust::make_counting_iterator(0), + thrust::make_counting_iterator(pb_ptr->n_constraints), + cuda::proclaim_return_type([v] __device__(i_t cstr_idx) { + return v.excess_score(cstr_idx, v.incumbent_lhs[cstr_idx]) * v.cstr_weights[cstr_idx]; + }), + (f_t)0, + thrust::plus()); + data.violation_score.set_value_async(violation, stream); + data.weighted_violation_score.set_value_async(weighted_violation, stream); + if ((context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS)) { + data.violated_constraints.sort(stream); + } +#if FJ_SINGLE_STEP + CUOPT_LOG_DEBUG("hash assignment %x, hash lhs %x, hash lhscomp %x", + detail::compute_hash(data.incumbent_assignment, stream), + detail::compute_hash(data.incumbent_lhs, stream), + detail::compute_hash(data.incumbent_lhs_sumcomp, stream)); + CUOPT_LOG_DEBUG("Violated constraints hash post sort: %x, index map %x", + detail::compute_hash(data.violated_constraints.contents, stream), + detail::compute_hash(data.violated_constraints.index_map, stream)); +#endif +} + +template +std::map fj_t::get_feature_vector(i_t climber_idx) const +{ + auto& data = *climbers[climber_idx]; + auto climber_stream = data.stream.view(); + if (climber_idx == 0) climber_stream = handle_ptr->get_stream(); + + std::map features; + + // Basic problem dimensions + features["n_variables"] = (float)pb_ptr->n_variables; + features["n_constraints"] = (float)pb_ptr->n_constraints; + features["nnz"] = (float)pb_ptr->coefficients.size(); + + // Matrix sparsity metrics (already computed) + features["sparsity"] = (float)pb_ptr->sparsity; + features["nnz_stddev"] = (float)pb_ptr->nnz_stddev; + features["unbalancedness"] = (float)pb_ptr->unbalancedness; + + // Algorithm settings + features["time"] = (float)settings.work_limit; + features["n_of_minimums_for_exit"] = (float)settings.n_of_minimums_for_exit; + features["feasibility_run"] = (float)settings.feasibility_run; + + // Variable type metrics + features["n_integer_vars"] = (float)pb_ptr->n_integer_vars; + features["n_binary_vars"] = (float)pb_ptr->n_binary_vars; + features["integer_ratio"] = + pb_ptr->n_variables > 0 ? (float)pb_ptr->n_integer_vars / pb_ptr->n_variables : 0.0f; + features["binary_ratio"] = + pb_ptr->n_variables > 0 ? (float)pb_ptr->n_binary_vars / pb_ptr->n_variables : 0.0f; + + // Initial violation metrics (from current state) + features["initial_violation_count"] = + (float)data.violated_constraints.set_size.value(climber_stream); + // features["initial_violation_score"] = (float)data.violation_score.value(climber_stream); + // features["initial_weighted_violation"] = + // (float)data.weighted_violation_score.value(climber_stream); + + // Load balancing decision + bool use_load_balancing = false; + if (settings.load_balancing_mode == fj_load_balancing_mode_t::ALWAYS_OFF) { + use_load_balancing = false; + } else if (settings.load_balancing_mode == fj_load_balancing_mode_t::ALWAYS_ON) { + use_load_balancing = true; + } else if (settings.load_balancing_mode == fj_load_balancing_mode_t::AUTO) { + use_load_balancing = + pb_ptr->n_variables > settings.parameters.load_balancing_codepath_min_varcount; + } + if (settings.mode == fj_mode_t::ROUNDING) { use_load_balancing = false; } + features["uses_load_balancing"] = (float)use_load_balancing; + + // Related variables metrics (if available) + if (pb_ptr->related_variables_offsets.size() > 0) { + auto h_offsets = cuopt::host_copy(pb_ptr->related_variables_offsets, handle_ptr->get_stream()); + i_t total_related = 0; + i_t max_related = 0; + for (i_t i = 0; i < pb_ptr->n_variables; ++i) { + i_t count = h_offsets[i + 1] - h_offsets[i]; + total_related += count; + max_related = std::max(max_related, count); + } + features["avg_related_vars_per_var"] = + pb_ptr->n_variables > 0 ? (float)total_related / pb_ptr->n_variables : 0.0f; + // features["max_related_vars"] = (float)max_related; + } else { + features["avg_related_vars_per_var"] = 0.0f; + // features["max_related_vars"] = 0.0f; + } + + // Constraint characteristics + auto h_lower = cuopt::host_copy(pb_ptr->constraint_lower_bounds, handle_ptr->get_stream()); + auto h_upper = cuopt::host_copy(pb_ptr->constraint_upper_bounds, handle_ptr->get_stream()); + i_t n_equality = 0; + i_t n_tight = 0; + f_t total_range = 0.0; + i_t n_range_constraints = 0; + + for (i_t i = 0; i < pb_ptr->n_constraints; ++i) { + if (pb_ptr->integer_equal(h_lower[i], h_upper[i])) { + n_equality++; + } else { + f_t range = h_upper[i] - h_lower[i]; + if (std::isfinite(range)) { + total_range += range; + n_range_constraints++; + if (range < 1.0) n_tight++; + } + } + } + features["equality_ratio"] = + pb_ptr->n_constraints > 0 ? (float)n_equality / pb_ptr->n_constraints : 0.0f; + features["avg_constraint_range"] = + n_range_constraints > 0 ? (float)(total_range / n_range_constraints) : 0.0f; + features["tight_constraint_ratio"] = + pb_ptr->n_constraints > 0 ? (float)n_tight / pb_ptr->n_constraints : 0.0f; + + // Variable bound characteristics + auto h_var_bounds = cuopt::host_copy(pb_ptr->variable_bounds, handle_ptr->get_stream()); + i_t n_unbounded = 0; + i_t n_fixed = 0; + f_t total_var_range = 0.0; + i_t n_bounded_vars = 0; + + for (i_t i = 0; i < pb_ptr->n_variables; ++i) { + f_t lower = get_lower(h_var_bounds[i]); + f_t upper = get_upper(h_var_bounds[i]); + + if (!std::isfinite(lower) || !std::isfinite(upper)) { + n_unbounded++; + } else if (pb_ptr->integer_equal(lower, upper)) { + n_fixed++; + } else { + f_t range = upper - lower; + total_var_range += range; + n_bounded_vars++; + } + } + features["unbounded_var_ratio"] = + pb_ptr->n_variables > 0 ? (float)n_unbounded / pb_ptr->n_variables : 0.0f; + features["fixed_var_ratio"] = + pb_ptr->n_variables > 0 ? (float)n_fixed / pb_ptr->n_variables : 0.0f; + features["avg_variable_range"] = + n_bounded_vars > 0 ? (float)(total_var_range / n_bounded_vars) : 0.0f; + + // Objective characteristics + auto h_obj_coeffs = cuopt::host_copy(pb_ptr->objective_coefficients, handle_ptr->get_stream()); + i_t n_obj_vars = 0; + f_t total_obj_magnitude = 0.0; + for (i_t i = 0; i < pb_ptr->n_variables; ++i) { + if (h_obj_coeffs[i] != 0.0) { + n_obj_vars++; + total_obj_magnitude += std::abs(h_obj_coeffs[i]); + } + } + features["obj_var_ratio"] = + pb_ptr->n_variables > 0 ? (float)n_obj_vars / pb_ptr->n_variables : 0.0f; + features["avg_obj_coeff_magnitude"] = + n_obj_vars > 0 ? (float)(total_obj_magnitude / n_obj_vars) : 0.0f; + + // Matrix density patterns + auto h_offsets = cuopt::host_copy(pb_ptr->offsets, handle_ptr->get_stream()); + i_t max_nnz_per_row = 0; + i_t min_nnz_per_row = pb_ptr->n_variables; + f_t sum_sq_dev = 0.0; + f_t mean_nnz = + pb_ptr->n_constraints > 0 ? (f_t)pb_ptr->coefficients.size() / pb_ptr->n_constraints : 0.0f; + + for (i_t i = 0; i < pb_ptr->n_constraints; ++i) { + i_t nnz_row = h_offsets[i + 1] - h_offsets[i]; + max_nnz_per_row = std::max(max_nnz_per_row, nnz_row); + min_nnz_per_row = std::min(min_nnz_per_row, nnz_row); + f_t dev = nnz_row - mean_nnz; + sum_sq_dev += dev * dev; + } + features["max_nnz_per_row"] = (float)max_nnz_per_row; + features["min_nnz_per_row"] = (float)min_nnz_per_row; + features["nnz_variance"] = + pb_ptr->n_constraints > 0 ? (float)(sum_sq_dev / pb_ptr->n_constraints) : 0.0f; + + // Average variable degree (avg constraints per variable) + features["avg_var_degree"] = + pb_ptr->n_variables > 0 ? (float)pb_ptr->coefficients.size() / pb_ptr->n_variables : 0.0f; + + // Derived complexity metrics + features["problem_size_score"] = + (float)(pb_ptr->n_variables * pb_ptr->n_constraints) * (float)pb_ptr->sparsity; + features["structural_complexity"] = + (features["integer_ratio"] + 1.0f) * (float)pb_ptr->unbalancedness; + features["constraint_var_ratio"] = + pb_ptr->n_variables > 0 ? (float)pb_ptr->n_constraints / pb_ptr->n_variables : 0.0f; + + return features; } template i_t fj_t::host_loop(solution_t& solution, i_t climber_idx) { - auto& data = *climbers[climber_idx]; - auto v = data.view(); // == climber_views[climber_idx] + auto& data = *climbers[climber_idx]; + auto v = data.view(); // == climber_views[climber_idx] + const double work_units_at_start = context.gpu_heur_loop.current_work(); + const bool publish_progress = (context.settings.determinism_mode & CUOPT_DETERMINISM_BB) && + context.branch_and_bound_ptr != nullptr && + std::isfinite(settings.work_limit) && settings.work_limit > 0.0 && + settings.iteration_limit > 0 && + settings.iteration_limit != std::numeric_limits::max(); auto climber_stream = data.stream.view(); if (climber_idx == 0) climber_stream = handle_ptr->get_stream(); @@ -865,7 +1083,7 @@ i_t fj_t::host_loop(solution_t& solution, i_t climber_idx) data.incumbent_quality.set_value_async(obj, handle_ptr->get_stream()); - timer_t timer(settings.time_limit); + work_limit_timer_t timer(context.gpu_heur_loop, settings.time_limit, *context.termination); i_t steps; bool limit_reached = false; for (steps = 0; steps < std::numeric_limits::max(); steps += iterations_per_graph) { @@ -879,9 +1097,10 @@ i_t fj_t::host_loop(solution_t& solution, i_t climber_idx) // every now and then, ensure external solutions are added to the population // this is done here because FJ is called within FP and also after recombiners // so FJ is one of the most inner and most frequent functions to be called - if (steps % 10000 == 0) { - context.diversity_manager_ptr->get_population_pointer() - ->add_external_solutions_to_population(); + if (steps % 10000 == 0 && context.diversity_manager_ptr != nullptr) { + auto* population_ptr = context.diversity_manager_ptr->get_population_pointer(); + cuopt_assert(population_ptr != nullptr, ""); + population_ptr->add_external_solutions_to_population(); } #if !FJ_SINGLE_STEP @@ -891,7 +1110,7 @@ i_t fj_t::host_loop(solution_t& solution, i_t climber_idx) CUOPT_LOG_TRACE( "FJ " "step %d viol %.2g [%d], obj %.8g, best %.8g, mins %d, maxw %g, " - "objw %g", + "objw %g, sol %x, delta %x, inc %x, lhs %x, lhscomp %x, viol %x, weights %x", steps, data.violation_score.value(climber_stream), data.violated_constraints.set_size.value(climber_stream), @@ -899,7 +1118,14 @@ i_t fj_t::host_loop(solution_t& solution, i_t climber_idx) data.best_objective.value(climber_stream), data.local_minimums_reached.value(climber_stream), max_cstr_weight.value(climber_stream), - objective_weight.value(climber_stream)); + objective_weight.value(climber_stream), + solution.get_hash(), + detail::compute_hash(data.jump_move_delta, climber_stream), + detail::compute_hash(data.incumbent_assignment, climber_stream), + detail::compute_hash(data.incumbent_lhs, climber_stream), + detail::compute_hash(data.incumbent_lhs_sumcomp, climber_stream), + detail::compute_hash(data.violated_constraints.contents, climber_stream), + detail::compute_hash(cstr_left_weights, climber_stream)); } if (!limit_reached) { run_step_device(climber_stream, climber_idx); } @@ -919,6 +1145,12 @@ i_t fj_t::host_loop(solution_t& solution, i_t climber_idx) } i_t iterations = data.iterations.value(climber_stream); + if (publish_progress) { + const double progress_ratio = + std::min(1.0, (double)iterations / (double)settings.iteration_limit); + const double published_work = work_units_at_start + settings.work_limit * progress_ratio; + context.gpu_heur_loop.set_current_work(published_work); + } // make sure we have the current incumbent saved (e.g. in the case of a timeout) update_best_solution_kernel<<<1, blocks_resetmoves, 0, climber_stream>>>(v); // check feasibility with the relative tolerance rather than the violation score @@ -969,6 +1201,9 @@ i_t fj_t::host_loop(solution_t& solution, i_t climber_idx) solution.get_feasible(), data.local_minimums_reached.value(climber_stream)); + // compute total time spent + double elapsed_time = timer.elapsed_time(); + CUOPT_LOG_TRACE("best fractional count %d", data.saved_best_fractional_count.value(climber_stream)); @@ -1058,16 +1293,47 @@ template i_t fj_t::solve(solution_t& solution) { raft::common::nvtx::range scope("fj_solve"); - timer_t timer(settings.time_limit); + bool deterministic = (context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS); + if (deterministic) { + settings.time_limit = std::max((f_t)0.0, settings.time_limit); + settings.work_limit = settings.time_limit; + } handle_ptr = const_cast(solution.handle_ptr); pb_ptr = solution.problem_ptr; if (settings.mode != fj_mode_t::ROUNDING) { cuopt_func_call(solution.test_variable_bounds(true)); cuopt_assert(solution.test_number_all_integer(), "All integers must be rounded"); } + if (deterministic && settings.work_limit == 0.0) { + CUOPT_LOG_DEBUG("FJ: skipping solve due to exhausted deterministic work budget"); + return solution.compute_feasibility(); + } + work_limit_timer_t timer(context.gpu_heur_loop, settings.time_limit, *context.termination); pb_ptr->check_problem_representation(true); resize_vectors(solution.handle_ptr); + CUOPT_LOG_DEBUG("FJ: work_limit %f time_limit %f sol hash %x pb hash %x", + settings.work_limit, + settings.time_limit, + solution.get_hash(), + pb_ptr->get_fingerprint()); + CUOPT_LOG_DEBUG("FJ: weights hash %x, left weights hash %x, right weights hash %x", + detail::compute_hash(cstr_weights, handle_ptr->get_stream()), + detail::compute_hash(cstr_left_weights, handle_ptr->get_stream()), + detail::compute_hash(cstr_right_weights, handle_ptr->get_stream())); + + // if work_limit is set: compute an estimate of the number of iterations required + if (deterministic && settings.work_limit != std::numeric_limits::infinity()) { + std::map features_map = get_feature_vector(0); + float iter_prediction = std::max( + (f_t)0.0, (f_t)ceil(context.work_unit_predictors.fj_predictor.predict_scalar(features_map))); + CUOPT_LOG_DEBUG("FJ determ: Estimated number of iterations for %f WU: %f", + settings.work_limit, + iter_prediction); + if (settings.work_limit == 0) iter_prediction = 0; + settings.iteration_limit = std::min(settings.iteration_limit, (i_t)iter_prediction); + } + bool is_initial_feasible = solution.compute_feasibility(); auto initial_solution = solution; // if we're in rounding mode, split the time/iteration limit between the first and second stage @@ -1102,6 +1368,9 @@ i_t fj_t::solve(solution_t& solution) RAFT_CHECK_CUDA(handle_ptr->get_stream()); handle_ptr->sync_stream(); + // Compute and store feature vector for later logging + if (deterministic) { feature_vector = get_feature_vector(0); } + i_t iterations = host_loop(solution); RAFT_CHECK_CUDA(handle_ptr->get_stream()); handle_ptr->sync_stream(); @@ -1149,6 +1418,53 @@ i_t fj_t::solve(solution_t& solution) cuopt_assert(solution.compute_feasibility(), "Reverted solution should be feasible"); } + cuopt_func_call(solution.test_variable_bounds()); + + if (deterministic) { + double work_to_record = settings.work_limit; + + if (iterations < settings.iteration_limit) { + CUOPT_LOG_DEBUG( + "FJ early exit at %d iterations (limit: %d)", iterations, settings.iteration_limit); + // Compute the work unit corresponding to the number of iterations elapsed + // by incrementally guessing work units until the model predicts >= actual iterations + // TODO: awfully ugly, change + if ((context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS) && + iterations > 0) { + double guessed_work = 0.0; + const double work_increment = 0.1; + const double max_work = settings.work_limit * 2.0; // Safety limit + float predicted_iters = 0.0f; + + // Make a copy of the feature vector and modify the time/work_limit field + std::map features_for_prediction = feature_vector; + + while (guessed_work <= max_work) { + features_for_prediction["time"] = (float)guessed_work; + predicted_iters = std::max( + 0.0f, + (float)ceil( + context.work_unit_predictors.fj_predictor.predict_scalar(features_for_prediction))); + + if (predicted_iters >= (float)iterations) { + work_to_record = guessed_work; + break; + } + + guessed_work += work_increment; + } + } + } + + CUOPT_LOG_DEBUG("FJ: recording work %fwu for %d iterations", work_to_record, iterations); + const double already_published_work = + std::max(0.0, context.gpu_heur_loop.current_work() - timer.work_units_at_start); + const double remaining_work_to_record = std::max(0.0, work_to_record - already_published_work); + timer.record_work(remaining_work_to_record); + } + + CUOPT_LOG_DEBUG("FJ sol hash %x", solution.get_hash()); + return is_new_feasible; } diff --git a/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump.cuh b/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump.cuh index e9040a7596..b9495cd282 100644 --- a/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump.cuh +++ b/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump.cuh @@ -19,6 +19,9 @@ #include +#include +#include + #define FJ_DEBUG_LOAD_BALANCING 0 #define FJ_SINGLE_STEP 0 @@ -99,6 +102,7 @@ struct fj_settings_t { fj_mode_t mode{fj_mode_t::FIRST_FEASIBLE}; fj_candidate_selection_t candidate_selection{fj_candidate_selection_t::WEIGHTED_SCORE}; double time_limit{60.0}; + double work_limit{std::numeric_limits::infinity()}; int iteration_limit{std::numeric_limits::max()}; fj_hyper_parameters_t parameters{}; int n_of_minimums_for_exit = 7000; @@ -129,8 +133,15 @@ struct fj_move_t { // as we dont need them to be floating point per the FJ2 scoring scheme // sizeof(fj_staged_score_t) <= 8 is needed to allow for atomic loads struct fj_staged_score_t { - float base{-std::numeric_limits::infinity()}; - float bonus{-std::numeric_limits::infinity()}; + int32_t base{std::numeric_limits::lowest()}; + int32_t bonus{std::numeric_limits::lowest()}; + + fj_staged_score_t() = default; + HDI fj_staged_score_t(int32_t base_, int32_t bonus_) : base(base_), bonus(bonus_) {} + fj_staged_score_t(const fj_staged_score_t&) = default; + fj_staged_score_t(fj_staged_score_t&&) = default; + fj_staged_score_t& operator=(const fj_staged_score_t&) = default; + fj_staged_score_t& operator=(fj_staged_score_t&&) = default; HDI bool operator<(fj_staged_score_t other) const noexcept { @@ -148,7 +159,7 @@ struct fj_staged_score_t { HDI static fj_staged_score_t invalid() { - return {-std::numeric_limits::infinity(), -std::numeric_limits::infinity()}; + return {std::numeric_limits::lowest(), std::numeric_limits::lowest()}; } HDI static fj_staged_score_t zero() { return {0, 0}; } @@ -628,6 +639,10 @@ class fj_t { std::vector> climbers; rmm::device_uvector climber_views; fj_settings_t settings; + std::map feature_vector; + + private: + std::map get_feature_vector(i_t climber_idx = 0) const; }; } // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump_impl_common.cuh b/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump_impl_common.cuh index e57f0ec9e2..44d3fe55d0 100644 --- a/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump_impl_common.cuh +++ b/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump_impl_common.cuh @@ -103,7 +103,9 @@ HDI std::pair feas_score_constraint( f_t cstr_coeff, f_t c_lb, f_t c_ub, - f_t current_lhs) + f_t current_lhs, + f_t cstr_left_weight = std::numeric_limits::quiet_NaN(), + f_t cstr_right_weight = std::numeric_limits::quiet_NaN()) { cuopt_assert(isfinite(delta), "invalid delta"); cuopt_assert(cstr_coeff != 0 && isfinite(cstr_coeff), "invalid coefficient"); @@ -123,14 +125,13 @@ HDI std::pair feas_score_constraint( // TODO: broadcast left/right weights to a csr_offset-indexed table? local minimums // usually occur on a rarer basis (around 50 iteratiosn to 1 local minimum) // likely unreasonable and overkill however - f_t cstr_weight = - bound_idx == 0 ? fj.cstr_left_weights[cstr_idx] : fj.cstr_right_weights[cstr_idx]; - f_t sign = bound_idx == 0 ? -1 : 1; - f_t rhs = bounds[bound_idx] * sign; - f_t old_lhs = current_lhs * sign; - f_t new_lhs = (current_lhs + cstr_coeff * delta) * sign; - f_t old_slack = rhs - old_lhs; - f_t new_slack = rhs - new_lhs; + f_t cstr_weight = bound_idx == 0 ? cstr_left_weight : cstr_right_weight; + f_t sign = bound_idx == 0 ? -1 : 1; + f_t rhs = bounds[bound_idx] * sign; + f_t old_lhs = current_lhs * sign; + f_t new_lhs = (current_lhs + cstr_coeff * delta) * sign; + f_t old_slack = rhs - old_lhs; + f_t new_slack = rhs - new_lhs; cuopt_assert(isfinite(cstr_weight), "invalid weight"); cuopt_assert(cstr_weight >= 0, "invalid weight"); diff --git a/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump_kernels.cu b/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump_kernels.cu index ebbb761277..335646d1bc 100644 --- a/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump_kernels.cu +++ b/cpp/src/mip_heuristics/feasibility_jump/feasibility_jump_kernels.cu @@ -14,6 +14,9 @@ #include +#include +#include + #include #include "feasibility_jump_impl_common.cuh" @@ -25,6 +28,22 @@ namespace cg = cooperative_groups; namespace cuopt::linear_programming::detail { +template +struct score_with_tiebreaker_comparator { + DI auto operator()(const thrust::pair& a, + const thrust::pair& b) const + { + auto a_score = a.first; + auto a_idx = a.second; + auto b_score = b.first; + auto b_idx = b.second; + + if (a_score > b_score) return a; + if (a_score == b_score && a_idx > b_idx) return a; + return b; + } +}; + template DI thrust::pair move_objective_score( const typename fj_t::climber_data_t::view_t& fj, i_t var_idx, f_t delta) @@ -152,10 +171,7 @@ __global__ void init_lhs_and_violation(typename fj_t::climber_data_t:: fj_kahan_babushka_neumaier_sum(delta_it + offset_begin, delta_it + offset_end); fj.incumbent_lhs_sumcomp[cstr_idx] = 0; - f_t th_violation = fj.excess_score(cstr_idx, fj.incumbent_lhs[cstr_idx]); - f_t weighted_violation = th_violation * fj.cstr_weights[cstr_idx]; - atomicAdd(fj.violation_score, th_violation); - atomicAdd(fj.weighted_violation_score, weighted_violation); + f_t th_violation = fj.excess_score(cstr_idx, fj.incumbent_lhs[cstr_idx]); f_t cstr_tolerance = fj.get_corrected_tolerance(cstr_idx); if (th_violation < -cstr_tolerance) { fj.violated_constraints.insert(cstr_idx); } } @@ -191,8 +207,17 @@ DI typename fj_t::move_score_info_t compute_new_score( f_t c_lb = fj.pb.constraint_lower_bounds[cstr_idx]; f_t c_ub = fj.pb.constraint_upper_bounds[cstr_idx]; - auto [cstr_base_feas, cstr_bonus_robust] = feas_score_constraint( - fj, var_idx, delta, cstr_idx, cstr_coeff, c_lb, c_ub, fj.incumbent_lhs[cstr_idx]); + auto [cstr_base_feas, cstr_bonus_robust] = + feas_score_constraint(fj, + var_idx, + delta, + cstr_idx, + cstr_coeff, + c_lb, + c_ub, + fj.incumbent_lhs[cstr_idx], + fj.cstr_left_weights[cstr_idx], + fj.cstr_right_weights[cstr_idx]); base_feas += cstr_base_feas; bonus_robust += cstr_bonus_robust; @@ -349,7 +374,7 @@ DI std::pair::move_score_info_t> compute_best_mtm( return std::make_pair(best_val, best_score_info); } -template +template DI void update_jump_value(typename fj_t::climber_data_t::view_t fj, i_t var_idx) { cuopt_assert(var_idx >= 0 && var_idx < fj.pb.n_variables, "invalid variable index"); @@ -376,12 +401,11 @@ DI void update_jump_value(typename fj_t::climber_data_t::view_t fj, i_ fj.pb.check_variable_within_bounds(var_idx, fj.incumbent_assignment[var_idx] + delta), "Var not within bounds!"); } - best_score_info = compute_new_score(fj, var_idx, delta); + best_score_info = compute_new_score(fj, var_idx, delta); } else { - auto [best_val, score_info] = - compute_best_mtm(fj, var_idx); - delta = best_val - fj.incumbent_assignment[var_idx]; - best_score_info = score_info; + auto [best_val, score_info] = compute_best_mtm(fj, var_idx); + delta = best_val - fj.incumbent_assignment[var_idx]; + best_score_info = score_info; } } else { delta = round(1.0 - 2 * fj.incumbent_assignment[var_idx]); @@ -577,14 +601,16 @@ __global__ void update_assignment_kernel(typename fj_t::climber_data_t __syncthreads(); - cuopt_assert(isfinite(fj.jump_move_delta[var_idx]), "delta should be finite"); - // Kahan compensated summation - // fj.incumbent_lhs[cstr_idx] = old_lhs + cstr_coeff * fj.jump_move_delta[var_idx]; - f_t y = cstr_coeff * fj.jump_move_delta[var_idx] - fj.incumbent_lhs_sumcomp[cstr_idx]; - f_t t = old_lhs + y; - fj.incumbent_lhs_sumcomp[cstr_idx] = (t - old_lhs) - y; - fj.incumbent_lhs[cstr_idx] = t; - cuopt_assert(isfinite(fj.incumbent_lhs[cstr_idx]), "assignment should be finite"); + if (threadIdx.x == 0) { + cuopt_assert(isfinite(fj.jump_move_delta[var_idx]), "delta should be finite"); + // Kahan compensated summation + // fj.incumbent_lhs[cstr_idx] = old_lhs + cstr_coeff * fj.jump_move_delta[var_idx]; + f_t y = cstr_coeff * fj.jump_move_delta[var_idx] - fj.incumbent_lhs_sumcomp[cstr_idx]; + f_t t = old_lhs + y; + fj.incumbent_lhs_sumcomp[cstr_idx] = (t - old_lhs) - y; + fj.incumbent_lhs[cstr_idx] = t; + cuopt_assert(isfinite(fj.incumbent_lhs[cstr_idx]), "assignment should be finite"); + } } // update the assignment and objective proper @@ -626,8 +652,8 @@ __global__ void update_assignment_kernel(typename fj_t::climber_data_t #if FJ_SINGLE_STEP DEVICE_LOG_DEBUG( - "=---- FJ[%d]: updated %d [%g/%g] :%.4g+{%.4g}=%.4g score {%g,%g}, d_obj %.2g+%.2g=%.2g, " - "err_range %.2g%%, infeas %.2g, total viol %d\n", + "=---- FJ[%d]: updated %d [%g/%g] :%.4g+{%.4g}=%.4g score {%d,%d}, d_obj %.2g+%.2g=%.2g, " + "err_range %.2g%%, infeas %.2g, total viol %d, obj %x, delta %x, coef %x\n", *fj.iterations, var_idx, get_lower(fj.pb.variable_bounds[var_idx]), @@ -642,7 +668,10 @@ __global__ void update_assignment_kernel(typename fj_t::climber_data_t *fj.incumbent_objective + fj.jump_move_delta[var_idx] * fj.pb.objective_coefficients[var_idx], delta_rel_err, fj.jump_move_infeasibility[var_idx], - fj.violated_constraints.size()); + fj.violated_constraints.size(), + detail::compute_hash(*fj.incumbent_objective), + detail::compute_hash(fj.jump_move_delta[var_idx]), + detail::compute_hash(fj.pb.objective_coefficients[var_idx])); #endif // reset the score fj.jump_move_scores[var_idx] = fj_t::move_score_t::invalid(); @@ -862,6 +891,15 @@ DI void update_changed_constraints(typename fj_t::climber_data_t::view if (blockIdx.x == 0) { if (threadIdx.x == 0) { + // sort changed constraints to guarantee determinism + // TODO: horribly slow as it is... block-parallelize at least? but not trivial for arbitrary + // sizes w/ CUB + if (fj.settings->work_limit != std::numeric_limits::infinity()) { + thrust::sort(thrust::seq, + fj.constraints_changed.begin(), + fj.constraints_changed.begin() + *fj.constraints_changed_count); + } + for (i_t i = 0; i < *fj.constraints_changed_count; ++i) { i_t idx = fj.constraints_changed[i]; if ((idx & 1) == CONSTRAINT_FLAG_INSERT) { @@ -953,7 +991,7 @@ __global__ void compute_iteration_related_variables_kernel( compute_iteration_related_variables(fj); } -template +template __device__ void compute_mtm_moves(typename fj_t::climber_data_t::view_t fj, bool ForceRefresh) { @@ -965,11 +1003,14 @@ __device__ void compute_mtm_moves(typename fj_t::climber_data_t::view_ if (*fj.selected_var == std::numeric_limits::max()) full_refresh = true; // always do a full sweep when looking for satisfied mtm moves - if constexpr (move_type == MTMMoveType::FJ_MTM_SATISFIED) full_refresh = true; - - // only update related variables i_t split_begin, split_end; - if (full_refresh) { + if constexpr (move_type == MTMMoveType::FJ_MTM_SATISFIED) { + full_refresh = true; + split_begin = 0; + split_end = fj.objective_vars.size(); + } + // only update related variables + else if (full_refresh) { split_begin = 0; split_end = fj.pb.n_variables; } @@ -992,9 +1033,15 @@ __device__ void compute_mtm_moves(typename fj_t::climber_data_t::view_ if (FIRST_THREAD) *fj.relvar_count_last_update = split_end - split_begin; for (i_t i = blockIdx.x + split_begin; i < split_end; i += gridDim.x) { - i_t var_idx = full_refresh ? i - : fj.pb.related_variables.size() == 0 ? i - : fj.pb.related_variables[i]; + // if sat MTM mode, go over objective variables only + i_t var_idx; + if constexpr (move_type == MTMMoveType::FJ_MTM_SATISFIED) { + var_idx = fj.objective_vars[i]; + } else { + var_idx = full_refresh ? i + : fj.pb.related_variables.size() == 0 ? i + : fj.pb.related_variables[i]; + } // skip if we couldnt precompute a related var table and // this variable isnt in the dynamic related variable table @@ -1017,7 +1064,7 @@ __device__ void compute_mtm_moves(typename fj_t::climber_data_t::view_ } cuopt_assert(var_idx >= 0 && var_idx < fj.pb.n_variables, ""); - update_jump_value(fj, var_idx); + update_jump_value(fj, var_idx); } } @@ -1025,7 +1072,7 @@ template __global__ void compute_mtm_moves_kernel(typename fj_t::climber_data_t::view_t fj, bool ForceRefresh) { - compute_mtm_moves(fj, ForceRefresh); + compute_mtm_moves(fj, ForceRefresh); } template @@ -1037,8 +1084,9 @@ __global__ void select_variable_kernel(typename fj_t::climber_data_t:: fj.settings->seed, *fj.iterations * fj.settings->parameters.max_sampled_moves, 0); using move_score_t = typename fj_t::move_score_t; - __shared__ alignas(move_score_t) char shmem_storage[2 * raft::WarpSize * sizeof(move_score_t)]; - auto* const shmem = (move_score_t*)shmem_storage; + __shared__ alignas(thrust::pair) char + shmem_storage[raft::WarpSize * sizeof(thrust::pair)]; + auto* const shmem = (thrust::pair*)shmem_storage; auto th_best_score = fj_t::move_score_t::invalid(); i_t th_selected_var = std::numeric_limits::max(); @@ -1075,8 +1123,11 @@ __global__ void select_variable_kernel(typename fj_t::climber_data_t:: } } // Block level reduction to get the best variable from the sample + // Use deterministic tie-breaking comparator based on var_idx auto [best_score, reduced_selected_var] = - raft::blockRankedReduce(th_best_score, shmem, th_selected_var, raft::max_op{}); + raft::blockReduce(thrust::make_pair(th_best_score, th_selected_var), + (char*)shmem, + score_with_tiebreaker_comparator{}); if (FIRST_THREAD) { // assign it to print the value outside th_best_score = best_score; @@ -1111,9 +1162,9 @@ __global__ void select_variable_kernel(typename fj_t::climber_data_t:: i_t var_range = get_upper(bounds) - get_lower(bounds); double delta_rel_err = fabs(fj.jump_move_delta[selected_var]) / var_range * 100; DEVICE_LOG_INFO( - "=---- FJ: selected %d [%g/%g] %c :%.4g+{%.4g}=%.4g score {%g,%g}, d_obj %.2g+%.2g->%.2g, " + "=---- FJ: selected %d [%g/%g] %c :%.4g+{%.4g}=%.4g score {%d,%d}, d_obj %.2g+%.2g->%.2g, " "delta_rel_err %.2g%%, " - "infeas %.2g, total viol %d, out of %d\n", + "infeas %.2g, total viol %d, out of %d, obj %x\n", selected_var, get_lower(bounds), get_upper(bounds), @@ -1130,9 +1181,18 @@ __global__ void select_variable_kernel(typename fj_t::climber_data_t:: delta_rel_err, fj.jump_move_infeasibility[selected_var], fj.violated_constraints.size(), - good_var_count); + good_var_count, + detail::compute_hash(*fj.incumbent_objective)); #endif cuopt_assert(fj.jump_move_scores[selected_var].valid(), ""); + } else { +#if FJ_SINGLE_STEP + DEVICE_LOG_INFO("=[%d]---- FJ: no var selected, obj is %g, viol %d, out of %d\n", + *fj.iterations, + *fj.incumbent_objective, + fj.violated_constraints.size(), + good_var_count); +#endif } } } @@ -1202,27 +1262,32 @@ DI thrust::tuple::move_score_t> gridwide_reduc if (blockIdx.x == 0) { using move_score_t = typename fj_t::move_score_t; - __shared__ alignas(move_score_t) char shmem_storage[2 * raft::WarpSize * sizeof(move_score_t)]; - auto* const shmem = (move_score_t*)shmem_storage; + __shared__ alignas(thrust::pair) char + shmem_storage[2 * raft::WarpSize * sizeof(thrust::pair)]; + auto* const shmem = (thrust::pair*)shmem_storage; auto th_best_score = fj_t::move_score_t::invalid(); i_t th_best_block = 0; + i_t th_best_var = -1; for (i_t i = threadIdx.x; i < gridDim.x; i += blockDim.x) { auto var_idx = fj.grid_var_buf[i]; auto move_score = fj.grid_score_buf[i]; - if (move_score > th_best_score || - (move_score == th_best_score && var_idx > fj.grid_var_buf[th_best_block])) { + if (move_score > th_best_score || (move_score == th_best_score && var_idx > th_best_var)) { th_best_score = move_score; th_best_block = i; + th_best_var = var_idx; } } // Block level reduction to get the best variable from all blocks - auto [reduced_best_score, reduced_best_block] = - raft::blockRankedReduce(th_best_score, shmem, th_best_block, raft::max_op{}); - - if (reduced_best_score.valid() && threadIdx.x == 0) { - cuopt_assert(th_best_block < gridDim.x, ""); + auto [reduced_best_score_pair, reduced_best_block] = + raft::blockRankedReduce(thrust::make_pair(th_best_score, th_best_var), + shmem, + th_best_block, + score_with_tiebreaker_comparator{}); + + if (reduced_best_score_pair.first.valid() && threadIdx.x == 0) { + cuopt_assert(reduced_best_block < gridDim.x, ""); best_var = fj.grid_var_buf[reduced_best_block]; best_delta = fj.grid_delta_buf[reduced_best_block]; best_score = fj.grid_score_buf[reduced_best_block]; @@ -1244,6 +1309,9 @@ DI thrust::tuple::move_score_t> best_random_mt raft::random::PCGenerator rng(fj.settings->seed + *fj.iterations, 0, 0); i_t cstr_idx = fj.violated_constraints.contents[rng.next_u32() % fj.violated_constraints.size()]; + cuopt_assert(fj.excess_score(cstr_idx, fj.incumbent_lhs[cstr_idx]) < 0, + "constraint isn't violated"); + auto [offset_begin, offset_end] = fj.pb.range_for_constraint(cstr_idx); return gridwide_reduce_best_move( @@ -1258,7 +1326,9 @@ DI thrust::tuple::move_score_t> best_sat_cstr_ typename fj_t::climber_data_t::view_t fj) { // compute all MTM moves within satisfied constraints - compute_mtm_moves(fj, true); + compute_mtm_moves(fj, true); + // NOTE: grid sync not required since each block only reduces over variables that it updated in + // compute_mtm_moves return gridwide_reduce_best_move( fj, fj.objective_vars.begin(), fj.objective_vars.end(), [fj] __device__(i_t var_idx) { return fj.jump_move_delta[var_idx]; @@ -1413,9 +1483,10 @@ __global__ void handle_local_minimum_kernel(typename fj_t::climber_dat if (sat_best_score.base > 0 && sat_best_score > best_score) { if (FIRST_THREAD) { - best_score = sat_best_score; - best_var = sat_best_var; - best_delta = sat_best_delta; + best_score = sat_best_score; + best_var = sat_best_var; + best_delta = sat_best_delta; + best_movetype = 'S'; } } } @@ -1427,6 +1498,15 @@ __global__ void handle_local_minimum_kernel(typename fj_t::climber_dat best_var, fj.incumbent_assignment[best_var] + best_delta), "assignment not within bounds"); fj.jump_move_delta[best_var] = best_delta; +#if FJ_SINGLE_STEP + DEVICE_LOG_DEBUG("FJ[%d] selected_var: %d, delta %g, score {%d %d}, type %c\n", + *fj.iterations, + best_var, + best_delta, + best_score.base, + best_score.bonus, + best_movetype); +#endif } } } diff --git a/cpp/src/mip_heuristics/feasibility_jump/fj_cpu.cu b/cpp/src/mip_heuristics/feasibility_jump/fj_cpu.cu index b3bc0d688e..1aead97bc0 100644 --- a/cpp/src/mip_heuristics/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip_heuristics/feasibility_jump/fj_cpu.cu @@ -11,11 +11,15 @@ #include "feasibility_jump_impl_common.cuh" #include "fj_cpu.cuh" +#include #include #include +#include #include +#include +#include #include #include #include @@ -38,6 +42,24 @@ namespace cuopt::linear_programming::detail { +namespace { + +double read_positive_work_unit_scale(const char* env_name) +{ + const char* env_value = std::getenv(env_name); + if (env_value == nullptr || env_value[0] == '\0') { return 1.0; } + + errno = 0; + char* end_ptr = nullptr; + const double parsed_value = std::strtod(env_value, &end_ptr); + const bool valid_value = errno == 0 && end_ptr != env_value && *end_ptr == '\0' && + std::isfinite(parsed_value) && parsed_value > 0.0; + cuopt_assert(valid_value, "Invalid CPUFJ work-unit scale env var"); + return parsed_value; +} + +} // namespace + template thrust::tuple get_mtm_for_bound(const typename fj_t::climber_data_t::view_t& fj, i_t var_idx, @@ -107,99 +129,6 @@ thrust::tuple get_mtm_for_constraint( return {delta_ij, sign, slack, cstr_tolerance}; } -template -std::pair feas_score_constraint(const typename fj_t::climber_data_t::view_t& fj, - i_t var_idx, - f_t delta, - i_t cstr_idx, - f_t cstr_coeff, - f_t c_lb, - f_t c_ub, - f_t current_lhs, - f_t left_weight, - f_t right_weight) -{ - cuopt_assert(isfinite(delta), "invalid delta"); - cuopt_assert(cstr_coeff != 0 && isfinite(cstr_coeff), "invalid coefficient"); - - f_t base_feas = 0; - f_t bonus_robust = 0; - - f_t bounds[2] = {c_lb, c_ub}; - cuopt_assert(isfinite(c_lb) || isfinite(c_ub), "no range"); - for (i_t bound_idx = 0; bound_idx < 2; ++bound_idx) { - if (!isfinite(bounds[bound_idx])) continue; - - // factor to correct the lhs/rhs to turn a lb <= lhs <= ub constraint into - // two virtual leq constraints "lhs <= ub" and "-lhs <= -lb" in order to match - // the convention of the paper - - // TODO: broadcast left/right weights to a csr_offset-indexed table? local minimums - // usually occur on a rarer basis (around 50 iteratiosn to 1 local minimum) - // likely unreasonable and overkill however - f_t cstr_weight = bound_idx == 0 ? left_weight : right_weight; - f_t sign = bound_idx == 0 ? -1 : 1; - f_t rhs = bounds[bound_idx] * sign; - f_t old_lhs = current_lhs * sign; - f_t new_lhs = (current_lhs + cstr_coeff * delta) * sign; - f_t old_slack = rhs - old_lhs; - f_t new_slack = rhs - new_lhs; - - cuopt_assert(isfinite(cstr_weight), "invalid weight"); - cuopt_assert(cstr_weight >= 0, "invalid weight"); - cuopt_assert(isfinite(old_lhs), ""); - cuopt_assert(isfinite(new_lhs), ""); - cuopt_assert(isfinite(old_slack) && isfinite(new_slack), ""); - - f_t cstr_tolerance = fj.get_corrected_tolerance(cstr_idx, c_lb, c_ub); - - bool old_viol = fj.excess_score(cstr_idx, current_lhs, c_lb, c_ub) < -cstr_tolerance; - bool new_viol = - fj.excess_score(cstr_idx, current_lhs + cstr_coeff * delta, c_lb, c_ub) < -cstr_tolerance; - - bool old_sat = old_lhs < rhs + cstr_tolerance; - bool new_sat = new_lhs < rhs + cstr_tolerance; - - // equality - if (fj.pb.integer_equal(c_lb, c_ub)) { - if (!old_viol) cuopt_assert(old_sat == !old_viol, ""); - if (!new_viol) cuopt_assert(new_sat == !new_viol, ""); - } - - // if it would feasibilize this constraint - if (!old_sat && new_sat) { - cuopt_assert(old_viol, ""); - base_feas += cstr_weight; - } - // would cause this constraint to be violated - else if (old_sat && !new_sat) { - cuopt_assert(new_viol, ""); - base_feas -= cstr_weight; - } - // simple improvement - else if (!old_sat && !new_sat && old_lhs > new_lhs) { - cuopt_assert(old_viol && new_viol, ""); - base_feas += (i_t)(cstr_weight * fj.settings->parameters.excess_improvement_weight); - } - // simple worsening - else if (!old_sat && !new_sat && old_lhs <= new_lhs) { - cuopt_assert(old_viol && new_viol, ""); - base_feas -= (i_t)(cstr_weight * fj.settings->parameters.excess_improvement_weight); - } - - // robustness score bonus if this would leave some strick slack - bool old_stable = old_lhs < rhs - cstr_tolerance; - bool new_stable = new_lhs < rhs - cstr_tolerance; - if (!old_stable && new_stable) { - bonus_robust += cstr_weight; - } else if (old_stable && !new_stable) { - bonus_robust -= cstr_weight; - } - } - - return {base_feas, bonus_robust}; -} - static constexpr double BIGVAL_THRESHOLD = 1e20; template @@ -1401,6 +1330,15 @@ std::unique_ptr> fj_t::create_cpu_climber( // Initialize fj_cpu with all the data init_fj_cpu(*fj_cpu, solution, left_weights, right_weights, objective_weight); + const double cpu_work_unit_scale = + context.settings.cpufj_work_unit_scale != 1.0 + ? context.settings.cpufj_work_unit_scale + : read_positive_work_unit_scale("CUOPT_CPUFJ_WORK_UNIT_SCALE"); + fj_cpu->work_unit_bias *= cpu_work_unit_scale; + if (cpu_work_unit_scale != 1.0) { + CUOPT_DETERMINISM_LOG( + "CPUFJ using work-unit scale %f (bias=%f)", cpu_work_unit_scale, fj_cpu->work_unit_bias); + } fj_cpu->settings = settings; if (randomize_params) { auto rng = std::mt19937(cuopt::seed_generator::get_seed()); @@ -1558,6 +1496,10 @@ bool fj_t::cpu_solve(fj_cpu_climber_t& fj_cpu, f_t in_time_l CUOPT_LOG_TRACE("CPUFJ work units: %f incumbent %g", fj_cpu.work_units_elapsed.load(std::memory_order_relaxed), fj_cpu.pb_ptr->get_user_obj_from_solver_obj(fj_cpu.h_best_objective)); + + if (fj_cpu.work_units_elapsed.load(std::memory_order_relaxed) >= fj_cpu.work_budget) { + break; + } } cuopt_func_call(sanity_checks(fj_cpu)); diff --git a/cpp/src/mip_heuristics/feasibility_jump/fj_cpu.cuh b/cpp/src/mip_heuristics/feasibility_jump/fj_cpu.cuh index 7dcc8d39b0..d1684a2774 100644 --- a/cpp/src/mip_heuristics/feasibility_jump/fj_cpu.cuh +++ b/cpp/src/mip_heuristics/feasibility_jump/fj_cpu.cuh @@ -154,7 +154,8 @@ struct fj_cpu_climber_t { // Work unit tracking for deterministic synchronization std::atomic work_units_elapsed{0.0}; - double work_unit_bias{1.5}; // Bias factor to keep CPUFJ ahead of B&B + double work_unit_bias{1.5}; // Bias factor to keep CPUFJ ahead of B&B + double work_budget{std::numeric_limits::infinity()}; producer_sync_t* producer_sync{nullptr}; // Optional sync utility for notifying progress std::atomic halted{false}; diff --git a/cpp/src/mip_heuristics/feasibility_jump/load_balancing.cuh b/cpp/src/mip_heuristics/feasibility_jump/load_balancing.cuh index dfc9b3c885..92df17ca34 100644 --- a/cpp/src/mip_heuristics/feasibility_jump/load_balancing.cuh +++ b/cpp/src/mip_heuristics/feasibility_jump/load_balancing.cuh @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -124,12 +124,14 @@ __global__ void load_balancing_prepare_iteration(const __grid_constant__ // alternate codepath in the case of a small related_var/total_var ratio if (!full_refresh && fj.pb.related_variables.size() > 0 && fj.pb.n_variables / fj.work_ids_for_related_vars[*fj.selected_var] >= - fj.settings->parameters.old_codepath_total_var_to_relvar_ratio_threshold) { + fj.settings->parameters.old_codepath_total_var_to_relvar_ratio_threshold && + fj.settings->load_balancing_mode != fj_load_balancing_mode_t::ALWAYS_ON) { auto range = fj.pb.range_for_related_vars(*fj.selected_var); for (i_t i = blockIdx.x + range.first; i < range.second; i += gridDim.x) { i_t var_idx = fj.pb.related_variables[i]; - update_jump_value(fj, var_idx); + update_jump_value(fj, + var_idx); } if (FIRST_THREAD) *fj.load_balancing_skip = true; @@ -334,8 +336,17 @@ __global__ void load_balancing_compute_scores_binary( auto c_lb = fj.constraint_lower_bounds_csr[csr_offset]; auto c_ub = fj.constraint_upper_bounds_csr[csr_offset]; - auto [cstr_base_feas, cstr_bonus_robust] = feas_score_constraint( - fj, var_idx, delta, cstr_idx, cstr_coeff, c_lb, c_ub, fj.incumbent_lhs[cstr_idx]); + auto [cstr_base_feas, cstr_bonus_robust] = + feas_score_constraint(fj, + var_idx, + delta, + cstr_idx, + cstr_coeff, + c_lb, + c_ub, + fj.incumbent_lhs[cstr_idx], + fj.cstr_left_weights[cstr_idx], + fj.cstr_right_weights[cstr_idx]); base_feas += cstr_base_feas; bonus_robust += cstr_bonus_robust; @@ -526,8 +537,8 @@ __launch_bounds__(TPB_loadbalance, 16) __global__ auto& score_info = candidate.score; - f_t base_feas = 0; - f_t bonus_robust = 0; + int32_t base_feas = 0; + int32_t bonus_robust = 0; // same as for the binary var kernel, compute each score compoenent per thread // and merge then via a wapr reduce @@ -535,8 +546,17 @@ __launch_bounds__(TPB_loadbalance, 16) __global__ cuopt_assert(c_lb == fj.pb.constraint_lower_bounds[cstr_idx], "bound sanity check failed"); cuopt_assert(c_ub == fj.pb.constraint_upper_bounds[cstr_idx], "bound sanity check failed"); - auto [cstr_base_feas, cstr_bonus_robust] = feas_score_constraint( - fj, var_idx, delta, cstr_idx, cstr_coeff, c_lb, c_ub, fj.incumbent_lhs[cstr_idx]); + auto [cstr_base_feas, cstr_bonus_robust] = + feas_score_constraint(fj, + var_idx, + delta, + cstr_idx, + cstr_coeff, + c_lb, + c_ub, + fj.incumbent_lhs[cstr_idx], + fj.cstr_left_weights[cstr_idx], + fj.cstr_right_weights[cstr_idx]); base_feas += cstr_base_feas; bonus_robust += cstr_bonus_robust; @@ -565,24 +585,29 @@ __launch_bounds__(TPB_loadbalance, 16) __global__ best_score_ref{fj.jump_move_scores[var_idx]}; auto best_score = best_score_ref.load(cuda::memory_order_relaxed); + cuda::atomic_ref best_delta_ref{ + fj.jump_move_delta[var_idx]}; + auto best_delta = best_delta_ref.load(cuda::memory_order_relaxed); + if (best_score < candidate.score || - (best_score == candidate.score && candidate.delta < fj.jump_move_delta[var_idx])) { + (best_score == candidate.score && candidate.delta < best_delta)) { // update the best move delta acquire_lock(&fj.jump_locks[var_idx]); // reject this move if it would increase the target variable to a numerically unstable // value - if (!fj.move_numerically_stable(fj.incumbent_assignment[var_idx], - fj.incumbent_assignment[var_idx] + delta, - base_feas, - *fj.violation_score)) { - fj.jump_move_scores[var_idx] = fj_t::move_score_t::invalid(); - } else if (fj.jump_move_scores[var_idx] < candidate.score - // determinism for ease of debugging - || (fj.jump_move_scores[var_idx] == candidate.score && - candidate.delta < fj.jump_move_delta[var_idx])) { - fj.jump_move_delta[var_idx] = candidate.delta; - fj.jump_move_scores[var_idx] = candidate.score; + // only skip updating, don't invalidate existing valid moves + if (fj.move_numerically_stable(fj.incumbent_assignment[var_idx], + fj.incumbent_assignment[var_idx] + delta, + base_feas, + *fj.violation_score)) { + if (fj.jump_move_scores[var_idx] < candidate.score + // determinism for ease of debugging + || (fj.jump_move_scores[var_idx] == candidate.score && + candidate.delta < fj.jump_move_delta[var_idx])) { + fj.jump_move_delta[var_idx] = candidate.delta; + fj.jump_move_scores[var_idx] = candidate.score; + } } release_lock(&fj.jump_locks[var_idx]); } @@ -644,7 +669,7 @@ __global__ void load_balancing_sanity_checks(const __grid_constant__ if (!(score_1 == score_1.invalid() && score_2 == score_2.invalid()) && !(v.pb.integer_equal(score_1.base, score_2.base) && v.pb.integer_equal(score_1.bonus, score_2.bonus))) { - printf("(iter %d) [%d, int:%d]: delta %g/%g was %f/%f, is %f/%f\n", + printf("(iter %d) [%d, int:%d]: delta %g/%g was %d/%d, is %d/%d\n", *v.iterations, var_idx, v.pb.is_integer_var(var_idx), diff --git a/cpp/src/mip_heuristics/feasibility_jump/utils.cuh b/cpp/src/mip_heuristics/feasibility_jump/utils.cuh index d98686bcc6..b779e56a21 100644 --- a/cpp/src/mip_heuristics/feasibility_jump/utils.cuh +++ b/cpp/src/mip_heuristics/feasibility_jump/utils.cuh @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -10,6 +10,7 @@ #include "feasibility_jump.cuh" #include +#include #include #include #include @@ -133,6 +134,21 @@ struct contiguous_set_t { validity_bitmap.resize(size, stream); } + void sort(const rmm::cuda_stream_view& stream) + { + thrust::sort( + rmm::exec_policy(stream), contents.begin(), contents.begin() + set_size.value(stream)); + thrust::fill(rmm::exec_policy(stream), index_map.begin(), index_map.end(), -1); + thrust::for_each(rmm::exec_policy(stream), + thrust::make_counting_iterator(0), + thrust::make_counting_iterator(set_size.value(stream)), + [v = view()] __device__(i_t idx) { v.index_map[v.contents[idx]] = idx; }); + + // TODO: remove, only useful for debugging and ensuring the same hashes + thrust::fill( + rmm::exec_policy(stream), contents.begin() + set_size.value(stream), contents.end(), 0); + } + struct view_t { i_t* set_size; i_t* lock; diff --git a/cpp/src/mip_heuristics/local_search/feasibility_pump/feasibility_pump.cu b/cpp/src/mip_heuristics/local_search/feasibility_pump/feasibility_pump.cu index f28faec249..1a62281450 100644 --- a/cpp/src/mip_heuristics/local_search/feasibility_pump/feasibility_pump.cu +++ b/cpp/src/mip_heuristics/local_search/feasibility_pump/feasibility_pump.cu @@ -5,6 +5,13 @@ */ /* clang-format on */ +// uncomment to enable detailed detemrinism logs +#undef CUOPT_DETERMINISM_LOG +#define CUOPT_DETERMINISM_LOG(...) \ + do { \ + CUOPT_LOG_INFO(__VA_ARGS__); \ + } while (0) + #include "feasibility_pump.cuh" #include @@ -52,7 +59,7 @@ feasibility_pump_t::feasibility_pump_t( context.problem_ptr->handle_ptr->get_stream()), lp_optimal_solution(lp_optimal_solution_), rng(cuopt::seed_generator::get_seed()), - timer(20.) + timer(20., *context.termination) { } @@ -147,18 +154,36 @@ bool feasibility_pump_t::linear_project_onto_polytope(solution_t temp_p(*solution.problem_ptr); auto h_integer_indices = cuopt::host_copy(solution.problem_ptr->integer_indices, solution.handle_ptr->get_stream()); + cuopt_assert(h_assignment.size() == solution.problem_ptr->n_variables, "Size mismatch"); + cuopt_assert(h_last_projection.size() == solution.problem_ptr->n_variables, "Size mismatch"); + cuopt_assert(h_variable_bounds.size() == solution.problem_ptr->n_variables, "Size mismatch"); + CUOPT_DETERMINISM_LOG( + "FP proj inputs: assign_hash=0x%x last_proj_hash=0x%x integer_idx_hash=0x%x n_vars=%d n_int=%d", + detail::compute_hash(h_assignment), + detail::compute_hash(h_last_projection), + detail::compute_hash(h_integer_indices), + solution.problem_ptr->n_variables, + solution.problem_ptr->n_integer_vars); f_t obj_offset = 0; + i_t n_at_upper = 0; + i_t n_at_lower = 0; + i_t n_interior = 0; + std::vector interior_integer_indices; + interior_integer_indices.reserve(h_integer_indices.size()); // for each integer add the variable and the distance constraints for (auto i : h_integer_indices) { + cuopt_assert(i >= 0 && i < solution.problem_ptr->n_variables, "Index out of bounds"); auto h_var_bounds = h_variable_bounds[i]; if (solution.problem_ptr->integer_equal(h_assignment[i], get_upper(h_var_bounds))) { obj_offset += get_upper(h_var_bounds); // set the objective weight to -1, u - x obj_coefficients[i] = -1; + n_at_upper++; } else if (solution.problem_ptr->integer_equal(h_assignment[i], get_lower(h_var_bounds))) { obj_offset -= get_lower(h_var_bounds); // set the objective weight to +1, x - l obj_coefficients[i] = 1; + n_at_lower++; } else { // objective weight is 1 const f_t obj_weight = 1.; @@ -183,9 +208,30 @@ bool feasibility_pump_t::linear_project_onto_polytope(solution_t constr_coeffs_2{1, 1}; h_constraints.add_constraint( constr_indices, constr_coeffs_2, h_assignment[i], (f_t)default_cont_upper); + n_interior++; + interior_integer_indices.push_back(i); } } + CUOPT_DETERMINISM_LOG( + "FP proj build: at_lower=%d at_upper=%d interior=%d interior_idx_hash=0x%x obj_hash=0x%x " + "assign_aug_hash=0x%x vars_added=%d cstr_added=%d cstr_var_hash=0x%x cstr_coeff_hash=0x%x " + "cstr_offset_hash=0x%x cstr_lb_hash=0x%x cstr_ub_hash=0x%x", + n_at_lower, + n_at_upper, + n_interior, + detail::compute_hash(interior_integer_indices), + detail::compute_hash(obj_coefficients), + detail::compute_hash(h_assignment), + h_variables.size(), + h_constraints.n_constraints(), + detail::compute_hash(h_constraints.constraint_variables), + detail::compute_hash(h_constraints.constraint_coefficients), + detail::compute_hash(h_constraints.constraint_offsets), + detail::compute_hash(h_constraints.constraint_lower_bounds), + detail::compute_hash(h_constraints.constraint_upper_bounds)); adjust_objective_with_original(solution, obj_coefficients, longer_lp_run); + CUOPT_DETERMINISM_LOG("FP proj adjusted objective hash=0x%x", + detail::compute_hash(obj_coefficients)); // commit all the changes that were done by the host if (h_variables.size() > 0) { temp_p.insert_variables(h_variables); } if (h_constraints.n_constraints() > 0) { temp_p.insert_constraints(h_constraints); } @@ -196,6 +242,12 @@ bool feasibility_pump_t::linear_project_onto_polytope(solution_tget_stream()), + temp_p.n_variables, + temp_p.n_constraints); // copy new objective coefficients raft::copy(temp_p.objective_coefficients.data(), obj_coefficients.data(), @@ -208,14 +260,20 @@ bool feasibility_pump_t::linear_project_onto_polytope(solution_t::round(solution_t& solution) { bool result; CUOPT_LOG_DEBUG("Rounding the point"); - timer_t bounds_prop_timer(std::max(0.05, std::min(0.5, timer.remaining_time() / 10.))); + f_t bounds_prop_time_limit = std::min((f_t)0.5, timer.remaining_time() / 10.); + if (timer.deterministic) { + bounds_prop_time_limit = std::max((f_t)0.0, bounds_prop_time_limit); + } else { + bounds_prop_time_limit = std::max((f_t)0.05, bounds_prop_time_limit); + } + work_limit_timer_t bounds_prop_timer( + context.gpu_heur_loop, bounds_prop_time_limit, *context.termination); const f_t lp_run_time_after_feasible = 0.; bool old_var = constraint_prop.round_all_vars; f_t old_time = constraint_prop.max_time_for_bounds_prop; @@ -307,6 +372,13 @@ bool feasibility_pump_t::test_fj_feasible(solution_t& soluti fj.settings.feasibility_run = true; fj.settings.n_of_minimums_for_exit = 5000; fj.settings.time_limit = std::min(time_limit, timer.remaining_time()); + if (timer.deterministic) { + fj.settings.time_limit = std::max((f_t)0.0, fj.settings.time_limit); + if (fj.settings.time_limit == 0.0) { + CUOPT_LOG_DEBUG("Skipping 20%% FJ run due to exhausted deterministic work budget"); + return false; + } + } cuopt_func_call(solution.test_variable_bounds(true)); is_feasible = fj.solve(solution); cuopt_func_call(solution.test_variable_bounds(true)); @@ -471,14 +543,38 @@ template bool feasibility_pump_t::run_single_fp_descent(solution_t& solution) { raft::common::nvtx::range fun_scope("run_single_fp_descent"); + i_t fp_iter = 0; + CUOPT_DETERMINISM_LOG("FP descent start: hash=0x%x feas=%d obj=%.12f timer_det=%d rem=%.6f", + solution.get_hash(), + (int)solution.get_feasible(), + solution.get_user_objective(), + (int)timer.deterministic, + timer.remaining_time()); // start by doing nearest rounding solution.round_nearest(); + CUOPT_DETERMINISM_LOG("FP descent after initial round: hash=0x%x feas=%d obj=%.12f", + solution.get_hash(), + (int)solution.get_feasible(), + solution.get_user_objective()); + cuopt_assert(last_projection.size() == solution.assignment.size(), "Size mismatch"); + // First projection in a descent has no previous projection history: initialize explicitly + raft::copy(last_projection.data(), + solution.assignment.data(), + solution.assignment.size(), + solution.handle_ptr->get_stream()); raft::copy(last_rounding.data(), solution.assignment.data(), solution.assignment.size(), solution.handle_ptr->get_stream()); while (true) { - if (context.diversity_manager_ptr->check_b_b_preemption() || timer.check_time_limit()) { + CUOPT_DETERMINISM_LOG("FP iter %d pre-projection: hash=0x%x feas=%d obj=%.12f rem=%.6f", + fp_iter, + solution.get_hash(), + (int)solution.get_feasible(), + solution.get_user_objective(), + timer.remaining_time()); + bool preempt = (context.diversity_manager_ptr->check_b_b_preemption()); + if (preempt || timer.check_time_limit()) { CUOPT_LOG_DEBUG("FP time limit reached!"); round(solution); return false; @@ -488,10 +584,25 @@ bool feasibility_pump_t::run_single_fp_descent(solution_t& s f_t ratio_of_assigned_integers = f_t(solution.n_assigned_integers) / solution.problem_ptr->n_integer_vars; bool is_feasible = linear_project_onto_polytope(solution, ratio_of_assigned_integers); - i_t n_integers = solution.compute_number_of_integers(); + const f_t remaining_after_projection = timer.remaining_time(); + i_t n_integers = solution.compute_number_of_integers(); CUOPT_LOG_DEBUG("after fp projection n_integers %d total n_integes %d", n_integers, solution.problem_ptr->n_integer_vars); + CUOPT_DETERMINISM_LOG( + "FP iter %d post-projection: hash=0x%x feasible_after_lp=%d obj=%.12f rem=%.6f lp_stage=%.6f", + fp_iter, + solution.get_hash(), + (int)is_feasible, + solution.get_user_objective(), + remaining_after_projection, + proj_begin - remaining_after_projection); + CUOPT_DETERMINISM_LOG("FP iter %d pre-round: hash=0x%x feas=%d obj=%.12f rem=%.6f", + fp_iter, + solution.get_hash(), + (int)is_feasible, + solution.get_user_objective(), + remaining_after_projection); bool is_cycle = true; // temp comment for presolve run if (config.check_distance_cycle) { @@ -523,30 +634,71 @@ bool feasibility_pump_t::run_single_fp_descent(solution_t& s // run the LP with full precision to check if it actually is feasible const f_t lp_verify_time_limit = 5.; relaxed_lp_settings_t lp_settings; - lp_settings.time_limit = lp_verify_time_limit; + lp_settings.time_limit = lp_verify_time_limit; + bool run_verify_lp = true; + if (timer.deterministic) { + const f_t remaining_work_limit = std::max((f_t)0.0, timer.remaining_time()); + lp_settings.work_limit = std::min(lp_verify_time_limit, remaining_work_limit); + lp_settings.time_limit = lp_settings.work_limit; + if (lp_settings.work_limit == 0.0) { + CUOPT_LOG_DEBUG( + "Skipping FP verification LP due to exhausted deterministic work budget"); + run_verify_lp = false; + } + } + lp_settings.work_context = timer.work_context; lp_settings.tolerance = solution.problem_ptr->tolerances.absolute_tolerance; lp_settings.return_first_feasible = true; lp_settings.save_state = true; - run_lp_with_vars_fixed(*solution.problem_ptr, - solution, - solution.problem_ptr->integer_indices, - lp_settings, - &constraint_prop.bounds_update); - is_feasible = solution.get_feasible(); - n_integers = solution.compute_number_of_integers(); - if (is_feasible && n_integers == solution.problem_ptr->n_integer_vars) { - CUOPT_LOG_DEBUG("Feasible solution verified with LP!"); - return true; + if (run_verify_lp) { + run_lp_with_vars_fixed(*solution.problem_ptr, + solution, + solution.problem_ptr->integer_indices, + lp_settings, + &constraint_prop.bounds_update); + is_feasible = solution.get_feasible(); + n_integers = solution.compute_number_of_integers(); + if (is_feasible && n_integers == solution.problem_ptr->n_integer_vars) { + CUOPT_LOG_TRACE("Feasible solution verified with LP!"); + return true; + } } } } cuopt_func_call(solution.test_variable_bounds(false)); is_feasible = round(solution); cuopt_func_call(solution.test_variable_bounds(true)); - proj_and_round_time = proj_begin - timer.remaining_time(); + const f_t remaining_after_round = timer.remaining_time(); + proj_and_round_time = proj_begin - remaining_after_round; + CUOPT_DETERMINISM_LOG( + "FP iter %d post-round: hash=0x%x feasible_after_round=%d obj=%.12f rem=%.6f " + "round_stage=%.6f proj_round_total=%.6f", + fp_iter, + solution.get_hash(), + (int)is_feasible, + solution.get_user_objective(), + remaining_after_round, + remaining_after_projection - remaining_after_round, + proj_and_round_time); if (!is_feasible) { const f_t time_ratio = 0.2; - is_feasible = test_fj_feasible(solution, time_ratio * proj_and_round_time); + const f_t fj_budget = time_ratio * proj_and_round_time; + CUOPT_DETERMINISM_LOG("FP iter %d pre-fj-fallback: hash=0x%x rem=%.6f fj_budget=%.6f", + fp_iter, + solution.get_hash(), + remaining_after_round, + fj_budget); + is_feasible = test_fj_feasible(solution, fj_budget); + const f_t remaining_after_fj = timer.remaining_time(); + CUOPT_DETERMINISM_LOG( + "FP iter %d post-fj-fallback: hash=0x%x feasible_after_fj=%d obj=%.12f rem=%.6f " + "fj_stage=%.6f", + fp_iter, + solution.get_hash(), + (int)is_feasible, + solution.get_user_objective(), + remaining_after_fj, + remaining_after_round - remaining_after_fj); } if (timer.check_time_limit()) { CUOPT_LOG_DEBUG("FP time limit reached!"); @@ -575,6 +727,7 @@ bool feasibility_pump_t::run_single_fp_descent(solution_t& s return false; } cycle_queue.n_iterations_without_cycle++; + fp_iter++; } // unreachable return false; diff --git a/cpp/src/mip_heuristics/local_search/feasibility_pump/feasibility_pump.cuh b/cpp/src/mip_heuristics/local_search/feasibility_pump/feasibility_pump.cuh index 4653e38615..2d1135b48b 100644 --- a/cpp/src/mip_heuristics/local_search/feasibility_pump/feasibility_pump.cuh +++ b/cpp/src/mip_heuristics/local_search/feasibility_pump/feasibility_pump.cuh @@ -105,7 +105,6 @@ class feasibility_pump_t { feasibility_pump_t() = delete; feasibility_pump_t(mip_solver_context_t& context, fj_t& fj, - // fj_tree_t& fj_tree_, constraint_prop_t& constraint_prop_, line_segment_search_t& line_segment_search_, rmm::device_uvector& lp_optimal_solution_); @@ -127,7 +126,7 @@ class feasibility_pump_t { bool check_distance_cycle(solution_t& solution); void reset(); void resize_vectors(problem_t& problem, const raft::handle_t* handle_ptr); - bool random_round_with_fj(solution_t& solution, timer_t& round_timer); + bool random_round_with_fj(solution_t& solution, work_limit_timer_t& round_timer); bool round_multiple_points(solution_t& solution); void relax_general_integers(solution_t& solution); void revert_relaxation(solution_t& solution); @@ -136,7 +135,6 @@ class feasibility_pump_t { mip_solver_context_t& context; // keep a reference from upstream local search fj_t& fj; - // fj_tree_t& fj_tree; line_segment_search_t& line_segment_search; cycle_queue_t cycle_queue; constraint_prop_t& constraint_prop; @@ -155,7 +153,7 @@ class feasibility_pump_t { f_t proj_begin; i_t n_fj_single_descents; i_t max_n_of_integers = 0; - cuopt::timer_t timer; + cuopt::work_limit_timer_t timer; }; } // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip_heuristics/local_search/line_segment_search/line_segment_search.cu b/cpp/src/mip_heuristics/local_search/line_segment_search/line_segment_search.cu index ce70aec745..094a45cd17 100644 --- a/cpp/src/mip_heuristics/local_search/line_segment_search/line_segment_search.cu +++ b/cpp/src/mip_heuristics/local_search/line_segment_search/line_segment_search.cu @@ -17,8 +17,10 @@ namespace cuopt::linear_programming::detail { template line_segment_search_t::line_segment_search_t( - fj_t& fj_, constraint_prop_t& constraint_prop_) - : fj(fj_), constraint_prop(constraint_prop_) + mip_solver_context_t& context_, + fj_t& fj_, + constraint_prop_t& constraint_prop_) + : context(context_), fj(fj_), constraint_prop(constraint_prop_) { } @@ -128,7 +130,7 @@ bool line_segment_search_t::search_line_segment( const rmm::device_uvector& point_2, const rmm::device_uvector& delta_vector, bool is_feasibility_run, - cuopt::timer_t& timer) + cuopt::work_limit_timer_t& timer) { CUOPT_LOG_DEBUG("Running line segment search with a given delta vector"); cuopt_assert(point_1.size() == point_2.size(), "size mismatch"); @@ -263,7 +265,7 @@ bool line_segment_search_t::search_line_segment(solution_t& const rmm::device_uvector& point_1, const rmm::device_uvector& point_2, bool is_feasibility_run, - cuopt::timer_t& timer) + cuopt::work_limit_timer_t& timer) { CUOPT_LOG_DEBUG("Running line segment search"); cuopt_assert(point_1.size() == point_2.size(), "size mismatch"); diff --git a/cpp/src/mip_heuristics/local_search/line_segment_search/line_segment_search.cuh b/cpp/src/mip_heuristics/local_search/line_segment_search/line_segment_search.cuh index 30e169e9d9..7a040ddbd2 100644 --- a/cpp/src/mip_heuristics/local_search/line_segment_search/line_segment_search.cuh +++ b/cpp/src/mip_heuristics/local_search/line_segment_search/line_segment_search.cuh @@ -9,7 +9,7 @@ #include #include -#include +#include namespace cuopt::linear_programming::detail { @@ -26,19 +26,21 @@ template class line_segment_search_t { public: line_segment_search_t() = delete; - line_segment_search_t(fj_t& fj, constraint_prop_t& constraint_prop); + line_segment_search_t(mip_solver_context_t& context, + fj_t& fj, + constraint_prop_t& constraint_prop); bool search_line_segment(solution_t& solution, const rmm::device_uvector& point_1, const rmm::device_uvector& point_2, bool is_feasibility_run, - cuopt::timer_t& timer); + cuopt::work_limit_timer_t& timer); bool search_line_segment(solution_t& solution, const rmm::device_uvector& point_1, const rmm::device_uvector& point_2, const rmm::device_uvector& delta_vector, bool is_feasibility_run, - cuopt::timer_t& timer); + cuopt::work_limit_timer_t& timer); void save_solution_if_better(solution_t& solution, const rmm::device_uvector& point_1, @@ -49,6 +51,7 @@ class line_segment_search_t { f_t& best_feasible_cost, f_t curr_cost); + mip_solver_context_t& context; fj_t& fj; constraint_prop_t& constraint_prop; line_segment_settings_t settings; diff --git a/cpp/src/mip_heuristics/local_search/local_search.cu b/cpp/src/mip_heuristics/local_search/local_search.cu index 118b7181ab..75dc93002c 100644 --- a/cpp/src/mip_heuristics/local_search/local_search.cu +++ b/cpp/src/mip_heuristics/local_search/local_search.cu @@ -5,6 +5,13 @@ */ /* clang-format on */ +// uncomment to enable detailed detemrinism logs +#undef CUOPT_DETERMINISM_LOG +#define CUOPT_DETERMINISM_LOG(...) \ + do { \ + CUOPT_LOG_INFO(__VA_ARGS__); \ + } while (0) + #include "lagrangian.cuh" #include "local_search.cuh" @@ -15,8 +22,9 @@ #include #include #include +#include #include -#include +#include #include @@ -36,7 +44,7 @@ local_search_t::local_search_t(mip_solver_context_t& context fj(context), // fj_tree(fj), constraint_prop(context), - line_segment_search(fj, constraint_prop), + line_segment_search(context, fj, constraint_prop), fp(context, fj, // fj_tree, @@ -53,16 +61,15 @@ local_search_t::local_search_t(mip_solver_context_t& context cpu_fj.fj_ptr = &fj; } scratch_cpu_fj_on_lp_opt.fj_ptr = &fj; + CUOPT_DETERMINISM_LOG("Deterministic solve start local_search state: seed_state=%lld", + (long long)cuopt::seed_generator::peek_seed()); } -static double local_search_best_obj = std::numeric_limits::max(); -static population_t* pop_ptr = nullptr; - template void local_search_t::start_cpufj_scratch_threads(population_t& population) { - pop_ptr = &population; - + cuopt_assert(!(context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS), + "Scratch CPUFJ must remain opportunistic-only"); std::vector default_weights(context.problem_ptr->n_constraints, 1.); solution_t solution(*context.problem_ptr); @@ -84,18 +91,9 @@ void local_search_t::start_cpufj_scratch_threads(population_tlog_prefix = "******* scratch " + std::to_string(counter) + ": "; cpu_fj.fj_cpu->improvement_callback = - [&population, problem_ptr = context.problem_ptr]( - f_t obj, const std::vector& h_vec, double /*work_units*/) { - population.add_external_solution(h_vec, obj, solution_origin_t::CPUFJ); - (void)problem_ptr; - if (obj < local_search_best_obj) { - CUOPT_LOG_TRACE("******* New local search best obj %g, best overall %g", - problem_ptr->get_user_obj_from_solver_obj(obj), - problem_ptr->get_user_obj_from_solver_obj( - population.is_feasible() ? population.best_feasible().get_objective() - : std::numeric_limits::max())); - local_search_best_obj = obj; - } + [&population](f_t obj, const std::vector& h_vec, double /*work_units*/) { + population.add_external_solution( + h_vec, obj, internals::mip_solution_origin_t::CPU_FEASIBILITY_JUMP); }; counter++; }; @@ -109,7 +107,8 @@ template void local_search_t::start_cpufj_lptopt_scratch_threads( population_t& population) { - pop_ptr = &population; + cuopt_assert(!(context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS), + "LP-opt CPUFJ scratch must remain opportunistic-only"); std::vector default_weights(context.problem_ptr->n_constraints, 1.); @@ -121,16 +120,9 @@ void local_search_t::start_cpufj_lptopt_scratch_threads( solution_lp, default_weights, default_weights, 0., context.preempt_heuristic_solver_); scratch_cpu_fj_on_lp_opt.fj_cpu->log_prefix = "******* scratch on LP optimal: "; scratch_cpu_fj_on_lp_opt.fj_cpu->improvement_callback = - [this, &population](f_t obj, const std::vector& h_vec, double /*work_units*/) { - population.add_external_solution(h_vec, obj, solution_origin_t::CPUFJ); - if (obj < local_search_best_obj) { - CUOPT_LOG_DEBUG("******* New local search best obj %g, best overall %g", - context.problem_ptr->get_user_obj_from_solver_obj(obj), - context.problem_ptr->get_user_obj_from_solver_obj( - population.is_feasible() ? population.best_feasible().get_objective() - : std::numeric_limits::max())); - local_search_best_obj = obj; - } + [&population](f_t obj, const std::vector& h_vec, double /*work_units*/) { + population.add_external_solution( + h_vec, obj, internals::mip_solution_origin_t::CPU_FEASIBILITY_JUMP); }; // default weights @@ -178,8 +170,11 @@ void local_search_t::start_cpufj_deterministic( // Set up callback to send solutions to B&B with work unit timestamps deterministic_cpu_fj.fj_cpu->improvement_callback = - [&bb](f_t obj, const std::vector& h_vec, double work_units) { - bb.queue_external_solution_deterministic(h_vec, work_units); + [&bb, problem_ptr = context.problem_ptr]( + f_t obj, const std::vector& h_vec, double work_units) { + f_t user_obj = problem_ptr->get_user_obj_from_solver_obj(obj); + bb.queue_external_solution_deterministic( + h_vec, user_obj, work_units, cuopt::internals::mip_solution_origin_t::CPU_FEASIBILITY_JUMP); }; deterministic_cpu_fj.start_cpu_solver(); @@ -207,8 +202,9 @@ bool local_search_t::do_fj_solve(solution_t& solution, const std::string& source) { if (time_limit == 0.) return solution.get_feasible(); + const bool deterministic = (context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS); - timer_t timer(time_limit); + work_limit_timer_t timer(context.gpu_heur_loop, time_limit, *context.termination); const auto old_n_cstr_weights = in_fj.cstr_weights.size(); const auto expected_n_cstr_weights = static_cast(solution.problem_ptr->n_constraints); // in case this is the first time run, resize @@ -227,16 +223,23 @@ bool local_search_t::do_fj_solve(solution_t& solution, 1.); } } - auto h_weights = cuopt::host_copy(in_fj.cstr_weights, solution.handle_ptr->get_stream()); - auto h_objective_weight = in_fj.objective_weight.value(solution.handle_ptr->get_stream()); - for (auto& cpu_fj : ls_cpu_fj) { - cpu_fj.fj_cpu = cpu_fj.fj_ptr->create_cpu_climber(solution, - h_weights, - h_weights, - h_objective_weight, - context.preempt_heuristic_solver_, - fj_settings_t{}, - true); + + { + auto h_weights = cuopt::host_copy(in_fj.cstr_weights, solution.handle_ptr->get_stream()); + auto h_objective_weight = in_fj.objective_weight.value(solution.handle_ptr->get_stream()); + for (auto& cpu_fj : ls_cpu_fj) { + cpu_fj.fj_cpu = cpu_fj.fj_ptr->create_cpu_climber(solution, + h_weights, + h_weights, + h_objective_weight, + context.preempt_heuristic_solver_, + fj_settings_t{}, + true); + if (deterministic) { + cpu_fj.fj_cpu->work_units_elapsed = 0.0; + cpu_fj.fj_cpu->work_budget = time_limit; + } + } } auto solution_copy = solution; @@ -251,9 +254,10 @@ bool local_search_t::do_fj_solve(solution_t& solution, in_fj.settings.time_limit = timer.remaining_time(); in_fj.solve(solution); - // Stop CPU solver - for (auto& cpu_fj : ls_cpu_fj) { - cpu_fj.stop_cpu_solver(); + if (!deterministic) { + for (auto& cpu_fj : ls_cpu_fj) { + cpu_fj.stop_cpu_solver(); + } } auto gpu_fj_end = std::chrono::high_resolution_clock::now(); @@ -262,7 +266,6 @@ bool local_search_t::do_fj_solve(solution_t& solution, solution_t solution_cpu(*solution.problem_ptr); f_t best_cpu_obj = std::numeric_limits::max(); - // // Wait for CPU solver to finish for (auto& cpu_fj : ls_cpu_fj) { bool cpu_sol_found = cpu_fj.wait_for_cpu_solver(); if (cpu_sol_found) { @@ -308,8 +311,10 @@ bool local_search_t::do_fj_solve(solution_t& solution, } template -void local_search_t::generate_fast_solution(solution_t& solution, timer_t timer) +void local_search_t::generate_fast_solution(solution_t& solution, + work_limit_timer_t timer) { + CUOPT_LOG_DEBUG("Running FJ fast sol"); thrust::fill(solution.handle_ptr->get_thrust_policy(), solution.assignment.begin(), solution.assignment.end(), @@ -320,8 +325,11 @@ void local_search_t::generate_fast_solution(solution_t& solu fj.settings.update_weights = true; fj.settings.feasibility_run = true; fj.settings.time_limit = std::min(30., timer.remaining_time()); - while (!context.diversity_manager_ptr->check_b_b_preemption() && !timer.check_time_limit()) { - timer_t constr_prop_timer = timer_t(std::min(timer.remaining_time(), 2.)); + while ((context.diversity_manager_ptr == nullptr || + !context.diversity_manager_ptr->check_b_b_preemption()) && + !timer.check_time_limit()) { + work_limit_timer_t constr_prop_timer = work_limit_timer_t( + context.gpu_heur_loop, std::min(timer.remaining_time(), 2.), *context.termination); // do constraint prop on lp optimal solution constraint_prop.apply_round(solution, 1., constr_prop_timer); if (solution.compute_feasibility()) { return; } @@ -338,7 +346,7 @@ void local_search_t::generate_fast_solution(solution_t& solu template bool local_search_t::run_local_search(solution_t& solution, const weight_t& weights, - timer_t timer, + work_limit_timer_t timer, const ls_config_t& ls_config) { raft::common::nvtx::range fun_scope("local search"); @@ -348,11 +356,10 @@ bool local_search_t::run_local_search(solution_t& solution, if (!solution.get_feasible()) { if (ls_config.at_least_one_parent_feasible) { fj_settings.time_limit = 0.5; - timer = timer_t(fj_settings.time_limit); } else { fj_settings.time_limit = 0.25; - timer = timer_t(fj_settings.time_limit); } + timer = work_limit_timer_t(context.gpu_heur_loop, fj_settings.time_limit, *context.termination); } else { fj_settings.time_limit = std::min(1., timer.remaining_time()); } @@ -382,8 +389,9 @@ bool local_search_t::run_local_search(solution_t& solution, template bool local_search_t::run_fj_until_timer(solution_t& solution, const weight_t& weights, - timer_t timer) + work_limit_timer_t timer) { + CUOPT_LOG_DEBUG("Running FJ until timer"); bool is_feasible; fj.settings.n_of_minimums_for_exit = 1e6; fj.settings.mode = fj_mode_t::EXIT_NON_IMPROVING; @@ -400,7 +408,7 @@ bool local_search_t::run_fj_until_timer(solution_t& solution template bool local_search_t::run_fj_annealing(solution_t& solution, - timer_t timer, + work_limit_timer_t timer, const ls_config_t& ls_config) { raft::common::nvtx::range fun_scope("run_fj_annealing"); @@ -430,7 +438,7 @@ bool local_search_t::run_fj_annealing(solution_t& solution, template bool local_search_t::run_fj_line_segment(solution_t& solution, - timer_t timer, + work_limit_timer_t timer, const ls_config_t& ls_config) { raft::common::nvtx::range fun_scope("run_fj_line_segment"); @@ -453,7 +461,7 @@ bool local_search_t::run_fj_line_segment(solution_t& solutio template bool local_search_t::check_fj_on_lp_optimal(solution_t& solution, bool perturb, - timer_t timer) + work_limit_timer_t timer) { raft::common::nvtx::range fun_scope("check_fj_on_lp_optimal"); if (lp_optimal_exists) { @@ -469,15 +477,21 @@ bool local_search_t::check_fj_on_lp_optimal(solution_t& solu solution.assign_random_within_bounds(perturbation_ratio); } cuopt_func_call(solution.test_variable_bounds(false)); - f_t lp_run_time_after_feasible = std::min(1., timer.remaining_time()); - timer_t bounds_prop_timer = timer_t(std::min(timer.remaining_time(), 10.)); + f_t lp_run_time_after_feasible = std::min(1., timer.remaining_time()); + work_limit_timer_t bounds_prop_timer = work_limit_timer_t( + context.gpu_heur_loop, std::min(timer.remaining_time(), 10.), *context.termination); bool is_feasible = constraint_prop.apply_round(solution, lp_run_time_after_feasible, bounds_prop_timer); if (!is_feasible) { const f_t lp_run_time = 2.; relaxed_lp_settings_t lp_settings; lp_settings.time_limit = std::min(lp_run_time, timer.remaining_time()); - lp_settings.tolerance = solution.problem_ptr->tolerances.absolute_tolerance; + if (timer.deterministic) { + lp_settings.work_limit = lp_settings.time_limit; + lp_settings.work_context = timer.work_context; + cuopt_assert(lp_settings.work_context != nullptr, "Missing deterministic work context"); + } + lp_settings.tolerance = solution.problem_ptr->tolerances.absolute_tolerance; run_lp_with_vars_fixed( *solution.problem_ptr, solution, solution.problem_ptr->integer_indices, lp_settings); } else { @@ -494,7 +508,8 @@ bool local_search_t::check_fj_on_lp_optimal(solution_t& solu } template -bool local_search_t::run_fj_on_zero(solution_t& solution, timer_t timer) +bool local_search_t::run_fj_on_zero(solution_t& solution, + work_limit_timer_t timer) { raft::common::nvtx::range fun_scope("run_fj_on_zero"); thrust::fill(solution.handle_ptr->get_thrust_policy(), @@ -513,7 +528,7 @@ bool local_search_t::run_fj_on_zero(solution_t& solution, ti template bool local_search_t::run_staged_fp(solution_t& solution, - timer_t timer, + work_limit_timer_t timer, population_t* population_ptr) { raft::common::nvtx::range fun_scope("run_staged_fp"); @@ -541,7 +556,8 @@ bool local_search_t::run_staged_fp(solution_t& solution, } CUOPT_LOG_DEBUG("Running staged FP from beginning it %d", i); fp.relax_general_integers(solution); - timer_t binary_timer(timer.remaining_time() / 3); + work_limit_timer_t binary_timer( + context.gpu_heur_loop, timer.remaining_time() / 3, *context.termination); i_t binary_it_counter = 0; for (; binary_it_counter < 100; ++binary_it_counter) { population_ptr->add_external_solutions_to_population(); @@ -653,7 +669,8 @@ void local_search_t::reset_alpha_and_save_solution( solution_t solution_copy(solution); solution_copy.problem_ptr = old_problem_ptr; solution_copy.resize_to_problem(); - population_ptr->add_solution(std::move(solution_copy)); + population_ptr->add_solution(std::move(solution_copy), + internals::mip_solution_origin_t::LOCAL_SEARCH); population_ptr->add_external_solutions_to_population(); if (!cutting_plane_added_for_active_run) { solution.problem_ptr = &problem_with_objective_cut; @@ -706,19 +723,20 @@ void local_search_t::reset_alpha_and_run_recombiners( template bool local_search_t::run_fp(solution_t& solution, - timer_t timer, - population_t* population_ptr) + work_limit_timer_t timer, + population_t* population_ptr, + i_t n_fp_iterations) { raft::common::nvtx::range fun_scope("run_fp"); cuopt_assert(population_ptr != nullptr, "Population pointer must not be null"); - const i_t n_fp_iterations = 1000000; bool is_feasible = solution.compute_feasibility(); cutting_plane_added_for_active_run = is_feasible; double best_objective = is_feasible ? solution.get_objective() : std::numeric_limits::max(); rmm::device_uvector best_solution(solution.assignment, solution.handle_ptr->get_stream()); problem_t* old_problem_ptr = solution.problem_ptr; - fp.timer = timer_t(timer.remaining_time()); + fp.timer = + work_limit_timer_t(context.gpu_heur_loop, timer.remaining_time(), *context.termination); // if it has not been initialized yet, create a new problem and move it to the cut problem if (!problem_with_objective_cut.cutting_plane_added) { problem_with_objective_cut = std::move(problem_t(*old_problem_ptr)); @@ -819,7 +837,7 @@ bool local_search_t::generate_solution(solution_t& solution, { raft::common::nvtx::range fun_scope("generate_solution"); cuopt_assert(population_ptr != nullptr, "Population pointer must not be null"); - timer_t timer(time_limit); + work_limit_timer_t timer(context.gpu_heur_loop, time_limit, *context.termination); auto n_vars = solution.problem_ptr->n_variables; auto n_binary_vars = solution.problem_ptr->get_n_binary_variables(); auto n_integer_vars = solution.problem_ptr->n_integer_vars; diff --git a/cpp/src/mip_heuristics/local_search/local_search.cuh b/cpp/src/mip_heuristics/local_search/local_search.cuh index a36688d71d..fc1dd6135c 100644 --- a/cpp/src/mip_heuristics/local_search/local_search.cuh +++ b/cpp/src/mip_heuristics/local_search/local_search.cuh @@ -13,13 +13,7 @@ #include #include #include -#include - -#include -#include -#include -#include -#include +#include namespace cuopt::linear_programming::dual_simplex { template @@ -58,32 +52,35 @@ class local_search_t { void start_cpufj_scratch_threads(population_t& population); void start_cpufj_lptopt_scratch_threads(population_t& population); void stop_cpufj_scratch_threads(); - void generate_fast_solution(solution_t& solution, timer_t timer); + void generate_fast_solution(solution_t& solution, work_limit_timer_t timer); bool generate_solution(solution_t& solution, bool perturb, population_t* population_ptr, f_t time_limit = 300.); bool run_fj_until_timer(solution_t& solution, const weight_t& weights, - timer_t timer); + work_limit_timer_t timer); bool run_local_search(solution_t& solution, const weight_t& weights, - timer_t timer, + work_limit_timer_t timer, const ls_config_t& ls_config); bool run_fj_annealing(solution_t& solution, - timer_t timer, + work_limit_timer_t timer, const ls_config_t& ls_config); bool run_fj_line_segment(solution_t& solution, - timer_t timer, + work_limit_timer_t timer, const ls_config_t& ls_config); - bool run_fj_on_zero(solution_t& solution, timer_t timer); - bool check_fj_on_lp_optimal(solution_t& solution, bool perturb, timer_t timer); + bool run_fj_on_zero(solution_t& solution, work_limit_timer_t timer); + bool check_fj_on_lp_optimal(solution_t& solution, + bool perturb, + work_limit_timer_t timer); bool run_staged_fp(solution_t& solution, - timer_t timer, + work_limit_timer_t timer, population_t* population_ptr); bool run_fp(solution_t& solution, - timer_t timer, - population_t* population_ptr = nullptr); + work_limit_timer_t timer, + population_t* population_ptr = nullptr, + i_t n_fp_iterations = std::numeric_limits::max()); void resize_vectors(problem_t& problem, const raft::handle_t* handle_ptr); bool do_fj_solve(solution_t& solution, diff --git a/cpp/src/mip_heuristics/local_search/rounding/bounds_repair.cu b/cpp/src/mip_heuristics/local_search/rounding/bounds_repair.cu index f3233cc8f4..23cd9b41e3 100644 --- a/cpp/src/mip_heuristics/local_search/rounding/bounds_repair.cu +++ b/cpp/src/mip_heuristics/local_search/rounding/bounds_repair.cu @@ -16,8 +16,95 @@ #include #include +#include + namespace cuopt::linear_programming::detail { +namespace { + +constexpr double bounds_repair_setup_base_work = 5e-4; +constexpr double bounds_repair_violation_base_work = 4e-4; +constexpr double bounds_repair_violation_nnz_work = 2e-6; +constexpr double bounds_repair_violation_constraint_work = 3e-6; +constexpr double bounds_repair_best_bounds_variable_work = 2e-6; +constexpr double bounds_repair_shift_base_work = 3e-4; +constexpr double bounds_repair_shift_row_entry_work = 3e-6; +constexpr double bounds_repair_shift_candidate_work = 8e-6; +constexpr double bounds_repair_shift_neighbor_entry_work = 3e-6; +constexpr double bounds_repair_shift_sort_work = 5e-6; +constexpr double bounds_repair_damage_base_work = 3e-4; +constexpr double bounds_repair_damage_neighbor_entry_work = 8e-6; +constexpr double bounds_repair_damage_sort_work = 5e-6; +constexpr double bounds_repair_move_base_work = 5e-5; +constexpr double bounds_repair_no_candidate_base_work = 4e-4; +constexpr double bounds_repair_cycle_penalty_work = 3e-4; + +template +double estimate_bounds_repair_violation_refresh_work(const problem_t& problem, + bool update_best_bounds) +{ + double estimate = bounds_repair_violation_base_work + + bounds_repair_violation_nnz_work * (double)problem.nnz + + bounds_repair_violation_constraint_work * (double)problem.n_constraints; + if (update_best_bounds) { + estimate += bounds_repair_best_bounds_variable_work * (double)problem.n_variables; + } + return estimate; +} + +template +double estimate_bounds_repair_setup_work(const problem_t& problem) +{ + return bounds_repair_setup_base_work + + estimate_bounds_repair_violation_refresh_work(problem, true); +} + +template +double estimate_bounds_repair_shift_work(const problem_t& problem, + i_t curr_cstr, + i_t n_candidates, + bool is_cycle) +{ + const auto stream = problem.handle_ptr->get_stream(); + const i_t cstr_begin = problem.offsets.element(curr_cstr, stream); + const i_t cstr_end = problem.offsets.element(curr_cstr + 1, stream); + const double row_nnz = cstr_end - cstr_begin; + const double avg_rev_degree = + problem.n_variables > 0 ? ((double)problem.nnz / (double)problem.n_variables) : 0.0; + const double sort_work = + n_candidates > 1 ? (double)n_candidates * std::log2((double)n_candidates) : 0.0; + double estimate = bounds_repair_shift_base_work + bounds_repair_shift_row_entry_work * row_nnz; + if (n_candidates == 0) { estimate = bounds_repair_no_candidate_base_work + estimate; } + estimate += bounds_repair_shift_candidate_work * (double)n_candidates; + estimate += bounds_repair_shift_neighbor_entry_work * (double)n_candidates * avg_rev_degree; + estimate += bounds_repair_shift_sort_work * sort_work; + if (is_cycle) { estimate += bounds_repair_cycle_penalty_work; } + return estimate; +} + +template +double estimate_bounds_repair_damage_work(const problem_t& problem, i_t n_candidates) +{ + if (n_candidates == 0) { return 0.0; } + const double avg_rev_degree = + problem.n_variables > 0 ? ((double)problem.nnz / (double)problem.n_variables) : 0.0; + const double sort_work = + n_candidates > 1 ? (double)n_candidates * std::log2((double)n_candidates) : 0.0; + return bounds_repair_damage_base_work + + bounds_repair_damage_neighbor_entry_work * (double)n_candidates * avg_rev_degree + + bounds_repair_damage_sort_work * sort_work; +} + +template +void record_estimated_work(timer_t& timer, double* total_estimated_work, double work) +{ + cuopt_assert(std::isfinite(work) && work >= 0.0, "Bounds repair work estimate must be finite"); + timer.record_work(work); + *total_estimated_work += work; +} + +} // namespace + template bounds_repair_t::bounds_repair_t(const problem_t& pb, bound_presolve_t& bound_presolve_) @@ -30,7 +117,8 @@ bounds_repair_t::bounds_repair_t(const problem_t& pb, violated_cstr_map(0, pb.handle_ptr->get_stream()), total_vio(pb.handle_ptr->get_stream()), gen(cuopt::seed_generator::get_seed()), - cycle_vector(MAX_CYCLE_SEQUENCE, -1) + cycle_vector(MAX_CYCLE_SEQUENCE, -1), + timer(0.0, cuopt::termination_checker_t::root_tag_t{}) { } @@ -68,8 +156,7 @@ f_t bounds_repair_t::get_ii_violation(problem_t& problem) min_act = bound_presolve.upd.min_activity.data(), max_act = bound_presolve.upd.max_activity.data(), cstr_violations_up = cstr_violations_up.data(), - cstr_violations_down = cstr_violations_down.data(), - total_vio = total_vio.data()] __device__(i_t cstr_idx) { + cstr_violations_down = cstr_violations_down.data()] __device__(i_t cstr_idx) { f_t cnst_lb = pb_v.constraint_lower_bounds[cstr_idx]; f_t cnst_ub = pb_v.constraint_upper_bounds[cstr_idx]; f_t eps = get_cstr_tolerance( @@ -79,21 +166,31 @@ f_t bounds_repair_t::get_ii_violation(problem_t& problem) f_t violation = max(curr_cstr_violation_up, curr_cstr_violation_down); if (violation >= ROUNDOFF_TOLERANCE) { violated_cstr_map[cstr_idx] = 1; - atomicAdd(total_vio, violation); } else { violated_cstr_map[cstr_idx] = 0; } cstr_violations_up[cstr_idx] = curr_cstr_violation_up; cstr_violations_down[cstr_idx] = curr_cstr_violation_down; }); - auto iter = thrust::copy_if(handle_ptr->get_thrust_policy(), + auto iter = thrust::copy_if(handle_ptr->get_thrust_policy(), thrust::make_counting_iterator(0), thrust::make_counting_iterator(0) + problem.n_constraints, violated_cstr_map.data(), violated_constraints.data(), cuda::std::identity{}); - h_n_violated_cstr = iter - violated_constraints.data(); - f_t total_violation = total_vio.value(handle_ptr->get_stream()); + h_n_violated_cstr = iter - violated_constraints.data(); + // Use deterministic reduction instead of non-deterministic atomicAdd + f_t total_violation = thrust::transform_reduce( + handle_ptr->get_thrust_policy(), + thrust::make_counting_iterator(0), + thrust::make_counting_iterator(0) + problem.n_constraints, + [cstr_violations_up = cstr_violations_up.data(), + cstr_violations_down = cstr_violations_down.data()] __device__(i_t cstr_idx) -> f_t { + auto violation = max(cstr_violations_up[cstr_idx], cstr_violations_down[cstr_idx]); + return violation >= ROUNDOFF_TOLERANCE ? violation : 0.; + }, + (f_t)0, + thrust::plus()); CUOPT_LOG_TRACE( "Repair: n_violated_cstr %d total_violation %f", h_n_violated_cstr, total_violation); return total_violation; @@ -190,7 +287,15 @@ i_t bounds_repair_t::compute_best_shift(problem_t& problem, } }); handle_ptr->sync_stream(); - return candidates.n_candidates.value(handle_ptr->get_stream()); + i_t n_candidates = candidates.n_candidates.value(handle_ptr->get_stream()); + + // Sort by variable index to ensure deterministic ordering + thrust::sort_by_key(handle_ptr->get_thrust_policy(), + candidates.variable_index.begin(), + candidates.variable_index.begin() + n_candidates, + candidates.bound_shift.begin()); + + return n_candidates; } template @@ -377,30 +482,88 @@ void bounds_repair_t::apply_move(problem_t& problem, template bool bounds_repair_t::repair_problem(problem_t& problem, problem_t& original_problem, - timer_t timer_, + work_limit_timer_t timer_, const raft::handle_t* handle_ptr_) { CUOPT_LOG_DEBUG("Running bounds repair"); handle_ptr = handle_ptr_; timer = timer_; + cuopt_assert(timer.deterministic == problem.deterministic, + "Bounds repair timer/problem determinism mismatch"); resize(problem); reset(); best_violation = get_ii_violation(problem); curr_violation = best_violation; best_bounds.update_from(problem, handle_ptr); + double total_estimated_work = 0.0; + i_t repair_iterations = 0; + if (timer.deterministic) { + const double setup_work = estimate_bounds_repair_setup_work(problem); + record_estimated_work(timer, &total_estimated_work, setup_work); + CUOPT_DETERMINISM_LOG( + "Repair entry: pb_hash=0x%x bounds_hash=0x%x violated_hash=0x%x n_violated=%d " + "best_violation=%.6f timer_rem=%.6f total_work=%.6f setup_work=%.6f", + problem.get_fingerprint(), + detail::compute_hash(make_span(problem.variable_bounds), handle_ptr->get_stream()), + detail::compute_hash(make_span(violated_constraints, 0, h_n_violated_cstr), + handle_ptr->get_stream()), + h_n_violated_cstr, + best_violation, + timer.remaining_time(), + total_estimated_work, + setup_work); + } i_t no_candidate_in_a_row = 0; - while (h_n_violated_cstr > 0) { + // TODO: do this better + i_t iter_limit = std::numeric_limits::max(); + if (timer.deterministic) { iter_limit = 20; } + while (h_n_violated_cstr > 0 && iter_limit-- > 0) { + repair_iterations++; CUOPT_LOG_TRACE("Bounds repair loop: n_violated %d best_violation %f curr_violation %f", h_n_violated_cstr, best_violation, curr_violation); + if (timer.deterministic) { + CUOPT_DETERMINISM_LOG( + "Repair iter entry: iter=%d pb_hash=0x%x bounds_hash=0x%x violated_hash=0x%x " + "n_violated=%d best_violation=%.6f curr_violation=%.6f timer_rem=%.6f total_work=%.6f", + repair_iterations, + problem.get_fingerprint(), + detail::compute_hash(make_span(problem.variable_bounds), handle_ptr->get_stream()), + detail::compute_hash(make_span(violated_constraints, 0, h_n_violated_cstr), + handle_ptr->get_stream()), + h_n_violated_cstr, + best_violation, + curr_violation, + timer.remaining_time(), + total_estimated_work); + } if (timer.check_time_limit()) { break; } i_t curr_cstr = get_random_cstr(); // best way would be to check a variable cycle, but this is easier and more performant bool is_cycle = detect_cycle(curr_cstr); if (is_cycle) { CUOPT_LOG_DEBUG("Repair: cycle detected at cstr %d", curr_cstr); } // in parallel compute the best shift and best respective damage - i_t n_candidates = compute_best_shift(problem, original_problem, curr_cstr); + i_t n_candidates = compute_best_shift(problem, original_problem, curr_cstr); + double shift_work = 0.0; + if (timer.deterministic) { + shift_work = estimate_bounds_repair_shift_work(problem, curr_cstr, n_candidates, is_cycle); + record_estimated_work(timer, &total_estimated_work, shift_work); + CUOPT_DETERMINISM_LOG( + "Repair iter shift: iter=%d curr_cstr=%d cycle=%d n_candidates=%d cand_var_hash=0x%x " + "cand_shift_hash=0x%x shift_work=%.6f timer_rem=%.6f total_work=%.6f", + repair_iterations, + curr_cstr, + (int)is_cycle, + n_candidates, + detail::compute_hash(make_span(candidates.variable_index, 0, n_candidates), + handle_ptr->get_stream()), + detail::compute_hash(make_span(candidates.bound_shift, 0, n_candidates), + handle_ptr->get_stream()), + shift_work, + timer.remaining_time(), + total_estimated_work); + } // if no candidate is there continue with another constraint if (n_candidates == 0) { CUOPT_LOG_DEBUG("Repair: no candidate var found for cstr %d", curr_cstr); @@ -415,12 +578,31 @@ bool bounds_repair_t::repair_problem(problem_t& problem, CUOPT_LOG_TRACE("Repair: number of candidates %d", n_candidates); // among the ones that have a valid shift value, compute the damage compute_damages(problem, n_candidates); + double damage_work = 0.0; + if (timer.deterministic) { + damage_work = estimate_bounds_repair_damage_work(problem, n_candidates); + record_estimated_work(timer, &total_estimated_work, damage_work); + CUOPT_DETERMINISM_LOG( + "Repair iter damage: iter=%d curr_cstr=%d cand_cdelta_hash=0x%x cand_damage_hash=0x%x " + "damage_work=%.6f timer_rem=%.6f total_work=%.6f", + repair_iterations, + curr_cstr, + detail::compute_hash(make_span(candidates.cstr_delta, 0, n_candidates), + handle_ptr->get_stream()), + detail::compute_hash(make_span(candidates.damage, 0, n_candidates), + handle_ptr->get_stream()), + damage_work, + timer.remaining_time(), + total_estimated_work); + } // get the best damage i_t best_cstr_delta = candidates.cstr_delta.front_element(handle_ptr->get_stream()); f_t best_damage = candidates.damage.front_element(handle_ptr->get_stream()); CUOPT_LOG_TRACE( "Repair: best_cstr_delta value %d best_damage %f", best_cstr_delta, best_damage); i_t best_move_idx; + i_t n_of_eligible_candidates = -1; + // if the best damage is positive and we are within the prop (paper uses 0.75) if ((best_cstr_delta > 0 && rand_double(0, 1, gen) < p) || is_cycle) { // pick a random move from the candidate list @@ -428,7 +610,7 @@ bool bounds_repair_t::repair_problem(problem_t& problem, } else { // filter the moves with best_damage(it can be zero or not) and then pick a candidate among // them - i_t n_of_eligible_candidates = + n_of_eligible_candidates = find_cutoff_index(candidates, best_cstr_delta, best_damage, n_candidates); cuopt_assert(n_of_eligible_candidates > 0, ""); CUOPT_LOG_TRACE("n_of_eligible_candidates %d", n_of_eligible_candidates); @@ -443,9 +625,38 @@ bool bounds_repair_t::repair_problem(problem_t& problem, apply_move(problem, original_problem, best_move_idx); reset(); // TODO we might optimize this to only calculate the changed constraints - curr_violation = get_ii_violation(problem); + curr_violation = get_ii_violation(problem); + const bool improved_violation = curr_violation < best_violation; + double refresh_work = 0.0; + if (timer.deterministic) { + refresh_work = bounds_repair_move_base_work + + estimate_bounds_repair_violation_refresh_work(problem, improved_violation); + record_estimated_work(timer, &total_estimated_work, refresh_work); + CUOPT_DETERMINISM_LOG( + "Repair iter post: iter=%d pb_hash=0x%x bounds_hash=0x%x violated_hash=0x%x " + "n_violated=%d curr_violation=%.6f improved=%d refresh_work=%.6f total_work=%.6f " + "timer_rem=%.6f", + repair_iterations, + problem.get_fingerprint(), + detail::compute_hash(make_span(problem.variable_bounds), handle_ptr->get_stream()), + detail::compute_hash(make_span(violated_constraints, 0, h_n_violated_cstr), + handle_ptr->get_stream()), + h_n_violated_cstr, + curr_violation, + (int)improved_violation, + refresh_work, + total_estimated_work, + timer.remaining_time()); + CUOPT_DETERMINISM_LOG( + "Repair iter work: cstr=%d candidates=%d cycle=%d improved=%d total=%.6f", + curr_cstr, + n_candidates, + (int)is_cycle, + (int)improved_violation, + total_estimated_work); + } - if (curr_violation < best_violation) { + if (improved_violation) { best_violation = curr_violation; // update best bounds best_bounds.update_from(problem, handle_ptr); diff --git a/cpp/src/mip_heuristics/local_search/rounding/bounds_repair.cuh b/cpp/src/mip_heuristics/local_search/rounding/bounds_repair.cuh index 29161c5d25..26bb84478e 100644 --- a/cpp/src/mip_heuristics/local_search/rounding/bounds_repair.cuh +++ b/cpp/src/mip_heuristics/local_search/rounding/bounds_repair.cuh @@ -120,7 +120,7 @@ class bounds_repair_t { void compute_damages(problem_t& problem, i_t n_candidates); bool repair_problem(problem_t& problem, problem_t& original_problem, - timer_t timer_, + work_limit_timer_t timer_, const raft::handle_t* handle_ptr_); void apply_move(problem_t& problem, problem_t& original_problem, @@ -144,7 +144,7 @@ class bounds_repair_t { i_t h_n_violated_cstr; const raft::handle_t* handle_ptr; std::mt19937 gen; - timer_t timer{0.}; + work_limit_timer_t timer; std::vector cycle_vector; i_t cycle_write_pos = 0; }; diff --git a/cpp/src/mip_heuristics/local_search/rounding/constraint_prop.cu b/cpp/src/mip_heuristics/local_search/rounding/constraint_prop.cu index 8db4d7ae85..ed371676cc 100644 --- a/cpp/src/mip_heuristics/local_search/rounding/constraint_prop.cu +++ b/cpp/src/mip_heuristics/local_search/rounding/constraint_prop.cu @@ -19,6 +19,8 @@ #include #include +#include + namespace cuopt::linear_programming::detail { template @@ -39,7 +41,8 @@ constraint_prop_t::constraint_prop_t(mip_solver_context_t& c ub_restore(context.problem_ptr->n_variables, context.problem_ptr->handle_ptr->get_stream()), assignment_restore(context.problem_ptr->n_variables, context.problem_ptr->handle_ptr->get_stream()), - rng(cuopt::seed_generator::get_seed(), 0, 0) + rng(cuopt::seed_generator::get_seed(), 0, 0), + max_timer(0.0, cuopt::termination_checker_t::root_tag_t{}) { } @@ -755,9 +758,11 @@ void constraint_prop_t::restore_original_bounds_on_unfixed( template bool constraint_prop_t::run_repair_procedure(problem_t& problem, problem_t& original_problem, - timer_t& timer, + work_limit_timer_t& timer, const raft::handle_t* handle_ptr) { + CUOPT_LOG_TRACE("Running repair procedure"); + // select the first probing value i_t select = 0; multi_probe.set_updated_bounds(problem, select, handle_ptr); @@ -765,9 +770,14 @@ bool constraint_prop_t::run_repair_procedure(problem_t& prob repair_stats.repair_attempts++; f_t repair_start_time = timer.remaining_time(); i_t n_of_repairs_needed_for_feasible = 0; + // TODO: do this better + i_t iter_limit = std::numeric_limits::max(); + if ((this->context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS)) { + iter_limit = 100; + } do { n_of_repairs_needed_for_feasible++; - if (timer.check_time_limit()) { + if (timer.check_time_limit() || iter_limit-- <= 0) { CUOPT_LOG_DEBUG("Time limit is reached in repair loop!"); f_t repair_end_time = timer.remaining_time(); repair_stats.total_time_spent_on_repair += repair_start_time - repair_end_time; @@ -841,7 +851,7 @@ bool constraint_prop_t::find_integer( solution_t& sol, solution_t& orig_sol, f_t lp_run_time_after_feasible, - timer_t& timer, + work_limit_timer_t& timer, std::optional>> probing_config) { using crit_t = termination_criterion_t; @@ -871,6 +881,7 @@ bool constraint_prop_t::find_integer( sol.problem_ptr->integer_indices.data(), sol.problem_ptr->n_integer_vars, sol.handle_ptr->get_stream()); + CUOPT_LOG_DEBUG("sol hash 0x%x", sol.get_hash()); } else { find_unset_integer_vars(sol, unset_integer_vars); sort_by_frac(sol, make_span(unset_integer_vars)); @@ -895,16 +906,17 @@ bool constraint_prop_t::find_integer( set_bounds_on_fixed_vars(sol); } - CUOPT_LOG_DEBUG("Bounds propagation rounding: unset vars %lu", unset_integer_vars.size()); + CUOPT_LOG_TRACE("Bounds propagation rounding: unset vars %lu", unset_integer_vars.size()); if (unset_integer_vars.size() == 0) { - CUOPT_LOG_DEBUG("No integer variables provided in the bounds prop rounding"); + CUOPT_LOG_TRACE("No integer variables provided in the bounds prop rounding"); expand_device_copy(orig_sol.assignment, sol.assignment, sol.handle_ptr->get_stream()); cuopt_func_call(orig_sol.test_variable_bounds()); return orig_sol.compute_feasibility(); } // this is needed for the sort inside of the loop bool problem_ii = is_problem_ii(*sol.problem_ptr); - // if the problem is ii, run the bounds prop in the beginning + CUOPT_LOG_TRACE("is problem ii %d", problem_ii); + // if the problem is ii, run the bounds prop in the beginning if (problem_ii) { bool bounds_repaired = bounds_repair.repair_problem(*sol.problem_ptr, *orig_sol.problem_ptr, timer, sol.handle_ptr); @@ -930,6 +942,8 @@ bool constraint_prop_t::find_integer( i_t n_failed_repair_iterations = 0; while (set_count < unset_integer_vars.size()) { CUOPT_LOG_TRACE("n_set_vars %d vars to set %lu", set_count, unset_integer_vars.size()); + CUOPT_LOG_TRACE("unset_integer_vars size %lu", unset_integer_vars.size()); + const size_t set_count_before = set_count; update_host_assignment(sol); if (max_timer.check_time_limit()) { CUOPT_LOG_DEBUG("Second time limit is reached returning nearest rounding!"); @@ -954,7 +968,8 @@ bool constraint_prop_t::find_integer( bounds_prop_interval = 1; } } - i_t n_vars_to_set = recovery_mode ? 1 : bounds_prop_interval; + i_t n_vars_to_set = recovery_mode ? 1 : bounds_prop_interval; + const bool did_sort = n_vars_to_set != 1; // if we are not at the last stage or if we are in recovery mode, don't sort if (n_vars_to_set != 1) { sort_by_implied_slack_consumption( @@ -969,9 +984,14 @@ bool constraint_prop_t::find_integer( generate_bulk_rounding_vector(sol, orig_sol, host_vars_to_set, probing_config); probe( sol, orig_sol.problem_ptr, var_probe_vals, &set_count, unset_integer_vars, probing_config); + [[maybe_unused]] bool repair_attempted = false; + bool bounds_repaired = false; + i_t n_fixed_vars = 0; if (!(n_failed_repair_iterations >= max_n_failed_repair_iterations) && rounding_ii && !timeout_happened) { - timer_t repair_timer{std::min(timer.remaining_time() / 5, timer.elapsed_time() / 3)}; + // timer_t repair_timer{std::min(timer.remaining_time() / 5, timer.elapsed_time() / 3)}; + work_limit_timer_t repair_timer( + context.gpu_heur_loop, timer.remaining_time() / 5, *context.termination); save_bounds(sol); // update bounds and run repair procedure bool bounds_repaired = @@ -998,7 +1018,7 @@ bool constraint_prop_t::find_integer( make_span(sol.problem_ptr->variable_bounds), make_span(orig_sol.problem_ptr->variable_bounds), make_span(sol.assignment)}); - i_t n_fixed_vars = (iter - (unset_vars.begin() + set_count)); + n_fixed_vars = (iter - (unset_vars.begin() + set_count)); CUOPT_LOG_TRACE("After repair procedure, number of additional fixed vars %d", n_fixed_vars); set_count += n_fixed_vars; } @@ -1026,7 +1046,7 @@ bool constraint_prop_t::find_integer( // which is the unchanged problem bounds multi_probe.update_host_bounds(sol.handle_ptr, make_span(sol.problem_ptr->variable_bounds)); } - CUOPT_LOG_DEBUG( + CUOPT_LOG_TRACE( "Bounds propagation rounding end: ii constraint count first buffer %d, second buffer %d", multi_probe.infeas_constraints_count_0, multi_probe.infeas_constraints_count_1); @@ -1038,7 +1058,12 @@ bool constraint_prop_t::find_integer( multi_probe.infeas_constraints_count_1 == 0) && !timeout_happened && lp_run_time_after_feasible > 0) { relaxed_lp_settings_t lp_settings; - lp_settings.time_limit = lp_run_time_after_feasible; + lp_settings.time_limit = lp_run_time_after_feasible; + if (timer.deterministic) { + lp_settings.work_limit = lp_settings.time_limit; + lp_settings.work_context = timer.work_context; + cuopt_assert(lp_settings.work_context != nullptr, "Missing deterministic work context"); + } lp_settings.tolerance = orig_sol.problem_ptr->tolerances.absolute_tolerance; lp_settings.save_state = false; lp_settings.return_first_feasible = true; @@ -1057,11 +1082,14 @@ template bool constraint_prop_t::apply_round( solution_t& sol, f_t lp_run_time_after_feasible, - timer_t& timer, + work_limit_timer_t& timer, std::optional>> probing_config) { raft::common::nvtx::range fun_scope("constraint prop round"); - max_timer = timer_t{max_time_for_bounds_prop}; + + sol.compute_feasibility(); + max_timer = + work_limit_timer_t{context.gpu_heur_loop, max_time_for_bounds_prop, *context.termination}; if (check_brute_force_rounding(sol)) { return true; } recovery_mode = false; rounding_ii = false; @@ -1076,9 +1104,9 @@ bool constraint_prop_t::apply_round( f_t bounds_prop_end_time = max_timer.remaining_time(); repair_stats.total_time_spent_on_bounds_prop += bounds_prop_start_time - bounds_prop_end_time; - CUOPT_LOG_DEBUG( - "repair_success %lu repair_attempts %lu intermediate_repair_success %lu total_repair_loops %lu " - "total_time_spent_on_repair %f total_time_spent_bounds_prop_after_repair %f " + CUOPT_LOG_TRACE( + "repair_success %lu repair_attempts %lu intermediate_repair_success %lu total_repair_loops" + "%lu total_time_spent_on_repair %f total_time_spent_bounds_prop_after_repair %f " "total_time_spent_on_bounds_prop %f", repair_stats.repair_success, repair_stats.repair_attempts, diff --git a/cpp/src/mip_heuristics/local_search/rounding/constraint_prop.cuh b/cpp/src/mip_heuristics/local_search/rounding/constraint_prop.cuh index 2c609228e8..7ad4253cc4 100644 --- a/cpp/src/mip_heuristics/local_search/rounding/constraint_prop.cuh +++ b/cpp/src/mip_heuristics/local_search/rounding/constraint_prop.cuh @@ -43,7 +43,7 @@ struct constraint_prop_t { constraint_prop_t(mip_solver_context_t& context); bool apply_round(solution_t& sol, f_t lp_run_time_after_feasible, - timer_t& timer, + work_limit_timer_t& timer, std::optional>> probing_config = std::nullopt); void sort_by_implied_slack_consumption(solution_t& sol, @@ -56,7 +56,7 @@ struct constraint_prop_t { bool find_integer(solution_t& sol, solution_t& orig_sol, f_t lp_run_time_after_feasible, - timer_t& timer, + work_limit_timer_t& timer, std::optional>> probing_config = std::nullopt); void find_set_integer_vars(solution_t& sol, rmm::device_uvector& set_vars); @@ -121,7 +121,7 @@ struct constraint_prop_t { const raft::handle_t* handle_ptr); bool run_repair_procedure(problem_t& problem, problem_t& original_problem, - timer_t& timer, + work_limit_timer_t& timer, const raft::handle_t* handle_ptr); bool handle_fixed_vars( solution_t& sol, @@ -149,7 +149,7 @@ struct constraint_prop_t { i_t bounds_prop_interval = 1; i_t n_iter_in_recovery = 0; i_t max_n_failed_repair_iterations = 1; - timer_t max_timer{0.}; + work_limit_timer_t max_timer; bool use_probing_cache = true; static repair_stats_t repair_stats; bool single_rounding_only = false; diff --git a/cpp/src/mip_heuristics/local_search/rounding/lb_bounds_repair.cu b/cpp/src/mip_heuristics/local_search/rounding/lb_bounds_repair.cu index 7d074aea5e..b3d53f43b2 100644 --- a/cpp/src/mip_heuristics/local_search/rounding/lb_bounds_repair.cu +++ b/cpp/src/mip_heuristics/local_search/rounding/lb_bounds_repair.cu @@ -26,7 +26,8 @@ lb_bounds_repair_t::lb_bounds_repair_t(const raft::handle_t* handle_pt violated_cstr_map(0, handle_ptr->get_stream()), total_vio(handle_ptr->get_stream()), gen(cuopt::seed_generator::get_seed()), - cycle_vector(MAX_CYCLE_SEQUENCE, -1) + cycle_vector(MAX_CYCLE_SEQUENCE, -1), + timer(0.0, cuopt::termination_checker_t::root_tag_t{}) { } @@ -68,8 +69,7 @@ std::tuple lb_bounds_repair_t::get_ii_violation( constraint_upper_bounds = problem.constraint_upper_bounds, cnst_slack = make_span_2(lb_bound_presolve.cnst_slack), cstr_violations_up = cstr_violations_up.data(), - cstr_violations_down = cstr_violations_down.data(), - total_vio = total_vio.data()] __device__(i_t cstr_idx) { + cstr_violations_down = cstr_violations_down.data()] __device__(i_t cstr_idx) { f_t cnst_lb = constraint_lower_bounds[cstr_idx]; f_t cnst_ub = constraint_upper_bounds[cstr_idx]; f_t2 slack = cnst_slack[cstr_idx]; @@ -80,7 +80,6 @@ std::tuple lb_bounds_repair_t::get_ii_violation( f_t violation = max(curr_cstr_violation_up, curr_cstr_violation_down); if (violation >= ROUNDOFF_TOLERANCE) { violated_cstr_map[cstr_idx] = 1; - atomicAdd(total_vio, violation); } else { violated_cstr_map[cstr_idx] = 0; } @@ -94,7 +93,18 @@ std::tuple lb_bounds_repair_t::get_ii_violation( violated_constraints.data(), cuda::std::identity{}); i_t n_violated_cstr = iter - violated_constraints.data(); - f_t total_violation = total_vio.value(handle_ptr->get_stream()); + // Use deterministic reduction instead of non-deterministic atomicAdd + f_t total_violation = thrust::transform_reduce( + handle_ptr->get_thrust_policy(), + thrust::make_counting_iterator(0), + thrust::make_counting_iterator(0) + problem.n_constraints, + [cstr_violations_up = cstr_violations_up.data(), + cstr_violations_down = cstr_violations_down.data()] __device__(i_t cstr_idx) -> f_t { + auto violation = max(cstr_violations_up[cstr_idx], cstr_violations_down[cstr_idx]); + return violation >= ROUNDOFF_TOLERANCE ? violation : 0.; + }, + (f_t)0, + thrust::plus()); CUOPT_LOG_TRACE( "Repair: n_violated_cstr %d total_violation %f", n_violated_cstr, total_violation); return std::make_tuple(total_violation, n_violated_cstr); @@ -400,7 +410,8 @@ bool lb_bounds_repair_t::repair_problem( timer_t timer_, const raft::handle_t* handle_ptr_) { - CUOPT_LOG_DEBUG("Running bounds repair"); + nvtx::range fun_scope("LB repair_problem"); + CUOPT_LOG_DEBUG("LB Running bounds repair"); handle_ptr = handle_ptr_; timer = timer_; resize(*problem); diff --git a/cpp/src/mip_heuristics/local_search/rounding/lb_bounds_repair.cuh b/cpp/src/mip_heuristics/local_search/rounding/lb_bounds_repair.cuh index 0b549c684d..3c4e4cf404 100644 --- a/cpp/src/mip_heuristics/local_search/rounding/lb_bounds_repair.cuh +++ b/cpp/src/mip_heuristics/local_search/rounding/lb_bounds_repair.cuh @@ -58,7 +58,7 @@ class lb_bounds_repair_t { bool repair_problem(load_balanced_problem_t* problem, load_balanced_bounds_presolve_t& lb_bound_presolve, problem_t& original_problem, - timer_t timer_, + work_limit_timer_t timer_, const raft::handle_t* handle_ptr_); void apply_move(load_balanced_problem_t* problem, problem_t& original_problem, @@ -82,7 +82,7 @@ class lb_bounds_repair_t { i_t h_n_violated_cstr; const raft::handle_t* handle_ptr; std::mt19937 gen; - timer_t timer{0.}; + work_limit_timer_t timer; std::vector cycle_vector; i_t cycle_write_pos = 0; }; diff --git a/cpp/src/mip_heuristics/local_search/rounding/lb_constraint_prop.cu b/cpp/src/mip_heuristics/local_search/rounding/lb_constraint_prop.cu index bb72834ab4..d8e3bcc040 100644 --- a/cpp/src/mip_heuristics/local_search/rounding/lb_constraint_prop.cu +++ b/cpp/src/mip_heuristics/local_search/rounding/lb_constraint_prop.cu @@ -33,7 +33,8 @@ lb_constraint_prop_t::lb_constraint_prop_t(mip_solver_context_thandle_ptr->get_stream()), assignment_restore(context.problem_ptr->n_variables, context.problem_ptr->handle_ptr->get_stream()), - rng(cuopt::seed_generator::get_seed(), 0, 0) + rng(cuopt::seed_generator::get_seed(), 0, 0), + max_timer(0.0, cuopt::termination_checker_t::root_tag_t{}) { } @@ -700,14 +701,15 @@ template bool lb_constraint_prop_t::apply_round( solution_t& sol, f_t lp_run_time_after_feasible, - timer_t& timer, + work_limit_timer_t& timer, std::optional>> probing_candidates) { raft::common::nvtx::range fun_scope("constraint prop round"); // this is second timer that can continue but without recovery mode const f_t max_time_for_bounds_prop = 5.; - max_timer = timer_t{max_time_for_bounds_prop}; + max_timer = + work_limit_timer_t{context.gpu_heur_loop, max_time_for_bounds_prop, *context.termination}; if (check_brute_force_rounding(sol)) { return true; } recovery_mode = false; rounding_ii = false; diff --git a/cpp/src/mip_heuristics/local_search/rounding/lb_constraint_prop.cuh b/cpp/src/mip_heuristics/local_search/rounding/lb_constraint_prop.cuh index 20e28e7cb9..6fb88467ab 100644 --- a/cpp/src/mip_heuristics/local_search/rounding/lb_constraint_prop.cuh +++ b/cpp/src/mip_heuristics/local_search/rounding/lb_constraint_prop.cuh @@ -23,7 +23,7 @@ struct lb_constraint_prop_t { bool apply_round( solution_t& sol, f_t lp_run_time_after_feasible, - timer_t& timer, + work_limit_timer_t& timer, std::optional>> probing_candidates = std::nullopt); void sort_by_implied_slack_consumption( problem_t& original_problem, @@ -40,7 +40,7 @@ struct lb_constraint_prop_t { load_balanced_bounds_presolve_t& lb_bounds_update, solution_t& orig_sol, f_t lp_run_time_after_feasible, - timer_t& timer, + work_limit_timer_t& timer, std::optional>> probing_candidates); std::tuple probing_values( load_balanced_bounds_presolve_t& lb_bounds_update, @@ -83,7 +83,7 @@ struct lb_constraint_prop_t { bool run_repair_procedure(load_balanced_problem_t* problem, load_balanced_bounds_presolve_t& lb_bounds_update, problem_t& original_problem, - timer_t& timer, + work_limit_timer_t& timer, const raft::handle_t* handle_ptr); mip_solver_context_t& context; @@ -100,7 +100,7 @@ struct lb_constraint_prop_t { bool rounding_ii = false; i_t bounds_prop_interval = 1; i_t n_iter_in_recovery = 0; - timer_t max_timer{0.}; + work_limit_timer_t max_timer; bool use_probing_cache = true; size_t repair_attempts = 0; diff --git a/cpp/src/mip_heuristics/local_search/rounding/simple_rounding.cu b/cpp/src/mip_heuristics/local_search/rounding/simple_rounding.cu index 4f3a015a6c..be19eaf78b 100644 --- a/cpp/src/mip_heuristics/local_search/rounding/simple_rounding.cu +++ b/cpp/src/mip_heuristics/local_search/rounding/simple_rounding.cu @@ -42,7 +42,8 @@ bool check_brute_force_rounding(solution_t& solution) rmm::device_uvector var_map(n_integers_to_round, solution.handle_ptr->get_stream()); rmm::device_uvector constraint_buf(n_configs * solution.problem_ptr->n_constraints, solution.handle_ptr->get_stream()); - rmm::device_scalar best_config(-1, solution.handle_ptr->get_stream()); + rmm::device_scalar best_config(std::numeric_limits::max(), + solution.handle_ptr->get_stream()); thrust::copy_if( solution.handle_ptr->get_thrust_policy(), solution.problem_ptr->integer_indices.begin(), @@ -58,7 +59,7 @@ bool check_brute_force_rounding(solution_t& solution) cuopt::make_span(var_map), cuopt::make_span(constraint_buf), best_config.data()); - if (best_config.value(solution.handle_ptr->get_stream()) != -1) { + if (best_config.value(solution.handle_ptr->get_stream()) != std::numeric_limits::max()) { CUOPT_LOG_DEBUG("Feasible found during brute force rounding!"); // apply the feasible rounding apply_feasible_rounding_kernel<<<1, TPB, 0, solution.handle_ptr->get_stream()>>>( diff --git a/cpp/src/mip_heuristics/local_search/rounding/simple_rounding_kernels.cuh b/cpp/src/mip_heuristics/local_search/rounding/simple_rounding_kernels.cuh index 2edca8fb08..a0b8468ea7 100644 --- a/cpp/src/mip_heuristics/local_search/rounding/simple_rounding_kernels.cuh +++ b/cpp/src/mip_heuristics/local_search/rounding/simple_rounding_kernels.cuh @@ -131,7 +131,7 @@ __global__ void brute_force_check_kernel(typename solution_t::view_t s __shared__ i_t shbuf[raft::WarpSize]; i_t total_feasible = raft::blockReduce(th_feasible_count, (char*)shbuf); if (threadIdx.x == 0) { - if (total_feasible == solution.problem.n_constraints) { atomicExch(best_config, config); } + if (total_feasible == solution.problem.n_constraints) { atomicMin(best_config, config); } } } diff --git a/cpp/src/mip_heuristics/presolve/bounds_presolve.cu b/cpp/src/mip_heuristics/presolve/bounds_presolve.cu index d78f8beb16..de6ae8c51a 100644 --- a/cpp/src/mip_heuristics/presolve/bounds_presolve.cu +++ b/cpp/src/mip_heuristics/presolve/bounds_presolve.cu @@ -171,6 +171,12 @@ termination_criterion_t bound_presolve_t::bound_update_loop(problem_t< { termination_criterion_t criteria = termination_criterion_t::ITERATION_LIMIT; + // CHANGE once we have a work predictor + if ((context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS)) { + timer = timer_t(std::numeric_limits::infinity()); + settings.iteration_limit = std::min(settings.iteration_limit, 50); + } + i_t iter; upd.init_changed_constraints(pb.handle_ptr); for (iter = 0; iter < settings.iteration_limit; ++iter) { diff --git a/cpp/src/mip_heuristics/presolve/bounds_update_data.cu b/cpp/src/mip_heuristics/presolve/bounds_update_data.cu index 487549aa4a..29eab4e69c 100644 --- a/cpp/src/mip_heuristics/presolve/bounds_update_data.cu +++ b/cpp/src/mip_heuristics/presolve/bounds_update_data.cu @@ -35,6 +35,35 @@ void bounds_update_data_t::resize(problem_t& problem) changed_constraints.resize(problem.n_constraints, problem.handle_ptr->get_stream()); next_changed_constraints.resize(problem.n_constraints, problem.handle_ptr->get_stream()); changed_variables.resize(problem.n_variables, problem.handle_ptr->get_stream()); + + thrust::fill(problem.handle_ptr->get_thrust_policy(), + min_activity.begin(), + min_activity.end(), + std::numeric_limits::signaling_NaN()); + thrust::fill(problem.handle_ptr->get_thrust_policy(), + max_activity.begin(), + max_activity.end(), + std::numeric_limits::signaling_NaN()); + thrust::fill(problem.handle_ptr->get_thrust_policy(), + lb.begin(), + lb.end(), + std::numeric_limits::signaling_NaN()); + thrust::fill(problem.handle_ptr->get_thrust_policy(), + ub.begin(), + ub.end(), + std::numeric_limits::signaling_NaN()); + thrust::fill(problem.handle_ptr->get_thrust_policy(), + changed_constraints.begin(), + changed_constraints.end(), + -1); + thrust::fill(problem.handle_ptr->get_thrust_policy(), + next_changed_constraints.begin(), + next_changed_constraints.end(), + -1); + thrust::fill(problem.handle_ptr->get_thrust_policy(), + changed_variables.begin(), + changed_variables.end(), + -1); } template diff --git a/cpp/src/mip_heuristics/presolve/lb_probing_cache.cu b/cpp/src/mip_heuristics/presolve/lb_probing_cache.cu index 3a6d1bce21..fc6c7fe4b6 100644 --- a/cpp/src/mip_heuristics/presolve/lb_probing_cache.cu +++ b/cpp/src/mip_heuristics/presolve/lb_probing_cache.cu @@ -309,7 +309,7 @@ inline std::vector compute_prioritized_integer_indices( template void compute_probing_cache(load_balanced_bounds_presolve_t& bound_presolve, load_balanced_problem_t& problem, - timer_t timer) + work_limit_timer_t timer) { // we dont want to compute the probing cache for all variables for time and computation resources auto priority_indices = compute_prioritized_integer_indices(bound_presolve, problem); @@ -400,7 +400,7 @@ void compute_probing_cache(load_balanced_bounds_presolve_t& bound_pres template void compute_probing_cache( \ load_balanced_bounds_presolve_t & bound_presolve, \ load_balanced_problem_t & problem, \ - timer_t timer); \ + work_limit_timer_t timer); \ template class lb_probing_cache_t; #if MIP_INSTANTIATE_FLOAT diff --git a/cpp/src/mip_heuristics/presolve/probing_cache.cu b/cpp/src/mip_heuristics/presolve/probing_cache.cu index 18f678c7e0..f152e440a8 100644 --- a/cpp/src/mip_heuristics/presolve/probing_cache.cu +++ b/cpp/src/mip_heuristics/presolve/probing_cache.cu @@ -367,7 +367,7 @@ void compute_cache_for_var(i_t var_idx, std::atomic& problem_is_infeasible, std::vector>& modification_vector, std::vector>& substitution_vector, - timer_t timer, + const work_limit_timer_t& timer, i_t device_id) { RAFT_CUDA_TRY(cudaSetDevice(device_id)); @@ -843,7 +843,7 @@ std::vector compute_priority_indices_by_implied_integers(problem_t bool compute_probing_cache(bound_presolve_t& bound_presolve, problem_t& problem, - timer_t timer) + work_limit_timer_t timer) { raft::common::nvtx::range fun_scope("compute_probing_cache"); // we dont want to compute the probing cache for all variables for time and computation resources @@ -857,6 +857,12 @@ bool compute_probing_cache(bound_presolve_t& bound_presolve, bound_presolve.settings.iteration_limit = 50; bound_presolve.settings.time_limit = timer.remaining_time(); + // TODO: proper work unit accounting in deterministic mode for the probing cache + if ((bound_presolve.context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS)) { + bound_presolve.settings.iteration_limit = 1; + priority_indices.resize(std::min(priority_indices.size(), 2048)); + } + size_t num_threads = bound_presolve.settings.num_threads < 0 ? 0.2 * omp_get_max_threads() : bound_presolve.settings.num_threads; @@ -944,7 +950,7 @@ bool compute_probing_cache(bound_presolve_t& bound_presolve, #define INSTANTIATE(F_TYPE) \ template bool compute_probing_cache(bound_presolve_t & bound_presolve, \ problem_t & problem, \ - timer_t timer); \ + work_limit_timer_t timer); \ template class probing_cache_t; #if MIP_INSTANTIATE_FLOAT diff --git a/cpp/src/mip_heuristics/presolve/probing_cache.cuh b/cpp/src/mip_heuristics/presolve/probing_cache.cuh index 91da6a15c8..76c4c5dfff 100644 --- a/cpp/src/mip_heuristics/presolve/probing_cache.cuh +++ b/cpp/src/mip_heuristics/presolve/probing_cache.cuh @@ -12,6 +12,7 @@ #include #include +#include namespace cuopt::linear_programming::detail { @@ -119,6 +120,6 @@ class lb_probing_cache_t { template bool compute_probing_cache(bound_presolve_t& bound_presolve, problem_t& problem, - timer_t timer); + work_limit_timer_t timer); } // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp b/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp index 2fa10f421b..af11265551 100644 --- a/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp +++ b/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp @@ -492,7 +492,8 @@ void check_postsolve_status(const papilo::PostsolveStatus& status) template void set_presolve_methods(papilo::Presolve& presolver, problem_category_t category, - bool dual_postsolve) + bool dual_postsolve, + bool deterministic) { using uptr = std::unique_ptr>; @@ -519,7 +520,7 @@ void set_presolve_methods(papilo::Presolve& presolver, // exhaustive presolvers presolver.addPresolveMethod(uptr(new papilo::ImplIntDetection())); presolver.addPresolveMethod(uptr(new papilo::DominatedCols())); - presolver.addPresolveMethod(uptr(new papilo::Probing())); + if (!deterministic) { presolver.addPresolveMethod(uptr(new papilo::Probing())); } if (!dual_postsolve) { presolver.addPresolveMethod(uptr(new papilo::DualInfer())); @@ -553,17 +554,20 @@ template void set_presolve_parameters(papilo::Presolve& presolver, problem_category_t category, int nrows, - int ncols) + int ncols, + bool deterministic = false) { // It looks like a copy. But this copy has the pointers to relevant variables in papilo auto params = presolver.getParameters(); if (category == problem_category_t::MIP) { - // Papilo has work unit measurements for probing. Because of this when the first batch fails to - // produce any reductions, the algorithm stops. To avoid stopping the algorithm, we set a - // minimum badge size to a huge value. The time limit makes sure that we exit if it takes too - // long - int min_badgesize = std::max(ncols / 2, 32); - params.setParameter("probing.minbadgesize", min_badgesize); + if (!deterministic) { + // Papilo has work unit measurements for probing. Because of this when the first batch fails + // to produce any reductions, the algorithm stops. To avoid stopping the algorithm, we set a + // minimum badge size to a huge value. The time limit makes sure that we exit if it takes too + // long + int min_badgesize = std::max(ncols / 2, 32); + params.setParameter("probing.minbadgesize", min_badgesize); + } params.setParameter("cliquemerging.enabled", true); params.setParameter("cliquemerging.maxcalls", 50); } @@ -632,7 +636,7 @@ std::optional> third_party_presolve_t papilo_presolver; - set_presolve_methods(papilo_presolver, category, dual_postsolve); + set_presolve_methods(papilo_presolver, category, dual_postsolve, deterministic_); set_presolve_options(papilo_presolver, category, absolute_tolerance, @@ -640,8 +644,11 @@ std::optional> third_party_presolve_t& reduced_costs, rmm::cuda_stream_view stream_view); + bool deterministic_ = false; + + public: + void set_deterministic(bool d) { deterministic_ = d; } + + private: bool maximize_ = false; cuopt::linear_programming::presolver_t presolver_ = cuopt::linear_programming::presolver_t::PSLP; // PSLP settings diff --git a/cpp/src/mip_heuristics/presolve/utils.cuh b/cpp/src/mip_heuristics/presolve/utils.cuh index 4870b3180c..404c614108 100644 --- a/cpp/src/mip_heuristics/presolve/utils.cuh +++ b/cpp/src/mip_heuristics/presolve/utils.cuh @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -12,6 +12,7 @@ namespace cuopt::linear_programming::detail { enum class termination_criterion_t { TIME_LIMIT, ITERATION_LIMIT, + WORK_LIMIT, CONVERGENCE, INFEASIBLE, NO_UPDATE diff --git a/cpp/src/mip_heuristics/problem/presolve_data.cu b/cpp/src/mip_heuristics/problem/presolve_data.cu index bf05efa875..e2e1a2b0a4 100644 --- a/cpp/src/mip_heuristics/problem/presolve_data.cu +++ b/cpp/src/mip_heuristics/problem/presolve_data.cu @@ -107,23 +107,25 @@ template void presolve_data_t::post_process_assignment( problem_t& problem, rmm::device_uvector& current_assignment, - bool resize_to_original_problem) + bool resize_to_original_problem, + const raft::handle_t* handle_override) { raft::common::nvtx::range fun_scope("post_process_assignment"); + const auto* h = handle_override ? handle_override : problem.handle_ptr; cuopt_assert(current_assignment.size() == variable_mapping.size(), "size mismatch"); auto assgn = make_span(current_assignment); auto fixed_assgn = make_span(fixed_var_assignment); auto var_map = make_span(variable_mapping); if (current_assignment.size() > 0) { - thrust::for_each(problem.handle_ptr->get_thrust_policy(), + thrust::for_each(h->get_thrust_policy(), thrust::make_counting_iterator(0), thrust::make_counting_iterator(current_assignment.size()), [fixed_assgn, var_map, assgn] __device__(auto idx) { fixed_assgn[var_map[idx]] = assgn[idx]; }); } - expand_device_copy(current_assignment, fixed_var_assignment, problem.handle_ptr->get_stream()); - auto h_assignment = cuopt::host_copy(current_assignment, problem.handle_ptr->get_stream()); + expand_device_copy(current_assignment, fixed_var_assignment, h->get_stream()); + auto h_assignment = cuopt::host_copy(current_assignment, h->get_stream()); cuopt_assert(additional_var_id_per_var.size() == h_assignment.size(), "Size mismatch"); cuopt_assert(additional_var_used.size() == h_assignment.size(), "Size mismatch"); for (i_t i = 0; i < (i_t)h_assignment.size(); ++i) { @@ -133,8 +135,6 @@ void presolve_data_t::post_process_assignment( } } - // Apply variable substitutions from probing: x_substituted = offset + coefficient * - // x_substituting for (const auto& sub : variable_substitutions) { cuopt_assert(sub.substituted_var < (i_t)h_assignment.size(), "substituted_var out of bounds"); cuopt_assert(sub.substituting_var < (i_t)h_assignment.size(), "substituting_var out of bounds"); @@ -148,14 +148,9 @@ void presolve_data_t::post_process_assignment( h_assignment[sub.substituted_var]); } - raft::copy(current_assignment.data(), - h_assignment.data(), - h_assignment.size(), - problem.handle_ptr->get_stream()); - // this separate resizing is needed because of the callback + raft::copy(current_assignment.data(), h_assignment.data(), h_assignment.size(), h->get_stream()); if (resize_to_original_problem) { - current_assignment.resize(problem.original_problem_ptr->get_n_variables(), - problem.handle_ptr->get_stream()); + current_assignment.resize(problem.original_problem_ptr->get_n_variables(), h->get_stream()); } } @@ -226,23 +221,23 @@ void presolve_data_t::set_papilo_presolve_data( template void presolve_data_t::papilo_uncrush_assignment( - problem_t& problem, rmm::device_uvector& assignment) const + problem_t& problem, + rmm::device_uvector& assignment, + const raft::handle_t* handle_override) const { if (papilo_presolve_ptr == nullptr) { CUOPT_LOG_INFO("Papilo presolve data not set, skipping uncrushing assignment"); return; } + const auto* h = handle_override ? handle_override : problem.handle_ptr; cuopt_assert(assignment.size() == papilo_reduced_to_original_map.size(), "Papilo uncrush assignment size mismatch"); - auto h_assignment = cuopt::host_copy(assignment, problem.handle_ptr->get_stream()); + auto h_assignment = cuopt::host_copy(assignment, h->get_stream()); std::vector full_assignment; papilo_presolve_ptr->uncrush_primal_solution(h_assignment, full_assignment); - assignment.resize(full_assignment.size(), problem.handle_ptr->get_stream()); - raft::copy(assignment.data(), - full_assignment.data(), - full_assignment.size(), - problem.handle_ptr->get_stream()); - problem.handle_ptr->sync_stream(); + assignment.resize(full_assignment.size(), h->get_stream()); + raft::copy(assignment.data(), full_assignment.data(), full_assignment.size(), h->get_stream()); + h->sync_stream(); } #if MIP_INSTANTIATE_FLOAT || PDLP_INSTANTIATE_FLOAT diff --git a/cpp/src/mip_heuristics/problem/presolve_data.cuh b/cpp/src/mip_heuristics/problem/presolve_data.cuh index 51b6bac95e..0495c2b088 100644 --- a/cpp/src/mip_heuristics/problem/presolve_data.cuh +++ b/cpp/src/mip_heuristics/problem/presolve_data.cuh @@ -89,7 +89,8 @@ class presolve_data_t { bool pre_process_assignment(problem_t& problem, rmm::device_uvector& assignment); void post_process_assignment(problem_t& problem, rmm::device_uvector& current_assignment, - bool resize_to_original_problem = true); + bool resize_to_original_problem = true, + const raft::handle_t* handle_override = nullptr); void post_process_solution(problem_t& problem, solution_t& solution); void set_papilo_presolve_data(const third_party_presolve_t* presolver_ptr, @@ -99,7 +100,8 @@ class presolve_data_t { bool has_papilo_presolve_data() const { return papilo_presolve_ptr != nullptr; } i_t get_papilo_original_num_variables() const { return papilo_original_num_variables; } void papilo_uncrush_assignment(problem_t& problem, - rmm::device_uvector& assignment) const; + rmm::device_uvector& assignment, + const raft::handle_t* handle_override = nullptr) const; presolve_data_t(presolve_data_t&&) = default; presolve_data_t& operator=(presolve_data_t&&) = default; diff --git a/cpp/src/mip_heuristics/problem/problem.cu b/cpp/src/mip_heuristics/problem/problem.cu index 90d80f5948..e82b185c38 100644 --- a/cpp/src/mip_heuristics/problem/problem.cu +++ b/cpp/src/mip_heuristics/problem/problem.cu @@ -64,6 +64,10 @@ void problem_t::op_problem_cstr_body(const optimization_problem_tget_thrust_policy(), + integer_fixed_variable_map.begin(), + integer_fixed_variable_map.end(), + -1); const bool is_mip = original_problem_ptr->get_problem_category() != problem_category_t::LP; if (is_mip) { @@ -136,7 +140,7 @@ problem_t::problem_t( nonbinary_indices(0, problem_.get_handle_ptr()->get_stream()), is_binary_variable(0, problem_.get_handle_ptr()->get_stream()), related_variables(0, problem_.get_handle_ptr()->get_stream()), - related_variables_offsets(n_variables, problem_.get_handle_ptr()->get_stream()), + related_variables_offsets(0, problem_.get_handle_ptr()->get_stream()), var_names(problem_.get_variable_names()), row_names(problem_.get_row_names()), objective_name(problem_.get_objective_name()), @@ -944,8 +948,12 @@ void problem_t::compute_related_variables(double time_limit) handle_ptr->sync_stream(); - // CHANGE - if (deterministic) { time_limit = std::numeric_limits::infinity(); } + if (deterministic) { + // TODO: Re-enable deterministic related-variable construction once we have a work estimator. + related_variables.resize(0, handle_ptr->get_stream()); + related_variables_offsets.resize(0, handle_ptr->get_stream()); + return; + } // previously used constants were based on 40GB of memory. Scale accordingly on smaller GPUs // We can't rely on querying free memory or allocation try/catch @@ -2138,9 +2146,11 @@ bool problem_t::pre_process_assignment(rmm::device_uvector& assig template void problem_t::post_process_assignment(rmm::device_uvector& current_assignment, - bool resize_to_original_problem) + bool resize_to_original_problem, + const raft::handle_t* handle_override) { - presolve_data.post_process_assignment(*this, current_assignment, resize_to_original_problem); + presolve_data.post_process_assignment( + *this, current_assignment, resize_to_original_problem, handle_override); } template @@ -2163,9 +2173,11 @@ void problem_t::set_papilo_presolve_data( } template -void problem_t::papilo_uncrush_assignment(rmm::device_uvector& assignment) const +void problem_t::papilo_uncrush_assignment(rmm::device_uvector& assignment, + const raft::handle_t* handle_override) const { - presolve_data.papilo_uncrush_assignment(const_cast(*this), assignment); + presolve_data.papilo_uncrush_assignment( + const_cast(*this), assignment, handle_override); } template diff --git a/cpp/src/mip_heuristics/problem/problem.cuh b/cpp/src/mip_heuristics/problem/problem.cuh index 9771bab568..7ae8c7553b 100644 --- a/cpp/src/mip_heuristics/problem/problem.cuh +++ b/cpp/src/mip_heuristics/problem/problem.cuh @@ -98,7 +98,8 @@ class problem_t { void preprocess_problem(); bool pre_process_assignment(rmm::device_uvector& assignment); void post_process_assignment(rmm::device_uvector& current_assignment, - bool resize_to_original_problem = true); + bool resize_to_original_problem = true, + const raft::handle_t* handle_override = nullptr); void post_process_solution(solution_t& solution); void set_papilo_presolve_data(const third_party_presolve_t* presolver_ptr, std::vector reduced_to_original, @@ -109,7 +110,8 @@ class problem_t { { return presolve_data.get_papilo_original_num_variables(); } - void papilo_uncrush_assignment(rmm::device_uvector& assignment) const; + void papilo_uncrush_assignment(rmm::device_uvector& assignment, + const raft::handle_t* handle_override = nullptr) const; void compute_transpose_of_problem(); f_t get_user_obj_from_solver_obj(f_t solver_obj) const; f_t get_solver_obj_from_user_obj(f_t user_obj) const; @@ -241,7 +243,8 @@ class problem_t { std::shared_ptr> integer_fixed_problem = nullptr; rmm::device_uvector integer_fixed_variable_map; - std::function&)> branch_and_bound_callback; + std::function&, cuopt::internals::mip_solution_origin_t)> + branch_and_bound_callback; std::function&, const std::vector&, const std::vector&, f_t, f_t, i_t)> set_root_relaxation_solution_callback; diff --git a/cpp/src/mip_heuristics/problem/problem_fixing.cuh b/cpp/src/mip_heuristics/problem/problem_fixing.cuh index 820b74e329..c462838d96 100644 --- a/cpp/src/mip_heuristics/problem/problem_fixing.cuh +++ b/cpp/src/mip_heuristics/problem/problem_fixing.cuh @@ -1,12 +1,13 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ #pragma once +#include #include namespace cuopt { @@ -18,6 +19,10 @@ struct problem_fixing_helpers_t { : reduction_in_rhs(n_constraints, handle_ptr->get_stream()), variable_fix_mask(n_variables, handle_ptr->get_stream()) { + thrust::fill( + handle_ptr->get_thrust_policy(), reduction_in_rhs.begin(), reduction_in_rhs.end(), f_t(0)); + thrust::fill( + handle_ptr->get_thrust_policy(), variable_fix_mask.begin(), variable_fix_mask.end(), i_t(0)); } problem_fixing_helpers_t(const problem_fixing_helpers_t& other, const raft::handle_t* handle_ptr) diff --git a/cpp/src/mip_heuristics/relaxed_lp/relaxed_lp.cu b/cpp/src/mip_heuristics/relaxed_lp/relaxed_lp.cu index e2bbc8feb1..d1fd0a6392 100644 --- a/cpp/src/mip_heuristics/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip_heuristics/relaxed_lp/relaxed_lp.cu @@ -21,6 +21,15 @@ #include +#include + +// uncomment to enable detailed detemrinism logs +#undef CUOPT_DETERMINISM_LOG +#define CUOPT_DETERMINISM_LOG(...) \ + do { \ + CUOPT_LOG_INFO(__VA_ARGS__); \ + } while (0) + namespace cuopt::linear_programming::detail { template @@ -40,6 +49,9 @@ optimization_problem_solution_t get_relaxed_lp_solution( const relaxed_lp_settings_t& settings) { raft::common::nvtx::range fun_scope("get_relaxed_lp_solution"); + static std::atomic lp_call_counter{0}; + const uint64_t lp_call_id = lp_call_counter.fetch_add(1, std::memory_order_relaxed); + pdlp_solver_settings_t pdlp_settings{}; pdlp_settings.detect_infeasibility = settings.check_infeasibility; pdlp_settings.set_optimality_tolerance(settings.tolerance); @@ -49,17 +61,57 @@ optimization_problem_solution_t get_relaxed_lp_solution( pdlp_settings.tolerances.relative_primal_tolerance = settings.tolerance / tolerance_divisor; pdlp_settings.tolerances.relative_dual_tolerance = settings.tolerance / tolerance_divisor; pdlp_settings.time_limit = settings.time_limit; - pdlp_settings.concurrent_halt = settings.concurrent_halt; - pdlp_settings.per_constraint_residual = settings.per_constraint_residual; - pdlp_settings.first_primal_feasible = settings.return_first_feasible; - pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; - pdlp_settings.presolver = presolver_t::None; + pdlp_settings.iteration_limit = settings.iteration_limit; + + const f_t work_limit = settings.work_limit; + const bool determinism_mode = std::isfinite(work_limit); + pdlp_settings.concurrent_halt = settings.concurrent_halt; + pdlp_settings.per_constraint_residual = settings.per_constraint_residual; + pdlp_settings.first_primal_feasible = settings.return_first_feasible; + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; + int estim_iters = pdlp_settings.iteration_limit; + if (determinism_mode) { + // try to estimate the iteration count based on the requested work limit + estim_iters = 100; + if (!std::isinf(work_limit)) { + do { + // TODO: use an actual predictor model here + double estim_ms = 313 + 200 * op_problem.n_variables - 400 * op_problem.n_constraints + + 600 * op_problem.coefficients.size() + 7100 * estim_iters; + estim_ms = std::max(0.0, estim_ms); + if (estim_ms > work_limit * 1000) { break; } + estim_iters += 100; + } while (true); + } else { + estim_iters = std::numeric_limits::max(); + } + CUOPT_DETERMINISM_LOG( + "estimated iterations %d for work limit %f", estim_iters, settings.work_limit); + pdlp_settings.iteration_limit = estim_iters; + pdlp_settings.time_limit = std::numeric_limits::infinity(); + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; + pdlp_settings.presolver = presolver_t::None; + } + CUOPT_DETERMINISM_LOG( + "LP call %lu config: det=%d work_limit=%.6f time_limit=%.6f iter_limit=%d method=%d mode=%d " + "presolver=%d save_state=%d has_initial=%d assignment_hash=0x%x", + lp_call_id, + (int)determinism_mode, + settings.work_limit, + pdlp_settings.time_limit, + pdlp_settings.iteration_limit, + (int)pdlp_settings.method, + (int)pdlp_settings.pdlp_solver_mode, + (int)pdlp_settings.presolver, + (int)settings.save_state, + (int)settings.has_initial_primal, + detail::compute_hash(assignment, op_problem.handle_ptr->get_stream())); set_pdlp_solver_mode(pdlp_settings); // TODO: set Stable3 here? pdlp_solver_t lp_solver(op_problem, pdlp_settings); if (settings.has_initial_primal) { i_t prev_size = lp_state.prev_dual.size(); - CUOPT_LOG_DEBUG( + CUOPT_LOG_TRACE( "setting initial primal solution of size %d dual size %d problem vars %d cstrs %d", assignment.size(), lp_state.prev_dual.size(), @@ -73,25 +125,68 @@ optimization_problem_solution_t get_relaxed_lp_solution( lp_state.prev_dual.data(), lp_state.prev_dual.data() + op_problem.n_constraints, [prev_size, dual = make_span(lp_state.prev_dual)] __device__(i_t i) { + // early exit to avoid a false positive in compute-sanitizer initcheck + if (i >= prev_size) { return 0.0; } f_t x = dual[i]; - if (!isfinite(x) || i >= prev_size) { return 0.0; } + if (!isfinite(x)) { return 0.0; } return x; }); lp_solver.set_initial_primal_solution(assignment); lp_solver.set_initial_dual_solution(lp_state.prev_dual); } - CUOPT_LOG_DEBUG( + CUOPT_LOG_TRACE( "running LP with n_vars %d n_cstr %d", op_problem.n_variables, op_problem.n_constraints); // before LP flush the logs as it takes quite some time cuopt::default_logger().flush(); // temporarily add timer auto start_time = timer_t(pdlp_settings.time_limit); lp_solver.set_inside_mip(true); + CUOPT_DETERMINISM_LOG( + "prev solution sizes primal=%lu dual=%lu", assignment.size(), lp_state.prev_dual.size()); + if (determinism_mode) { + auto init_primal_hash = + detail::compute_hash(make_span(assignment), op_problem.handle_ptr->get_stream()); + auto init_dual_hash = + settings.has_initial_primal + ? detail::compute_hash(make_span(lp_state.prev_dual), op_problem.handle_ptr->get_stream()) + : 0u; + CUOPT_DETERMINISM_LOG("LP call %lu pre-solve state: init_primal_hash=0x%x init_dual_hash=0x%x", + lp_call_id, + init_primal_hash, + init_dual_hash); + } auto solver_response = lp_solver.run_solver(start_time); + CUOPT_DETERMINISM_LOG("post LP primal size %lu", solver_response.get_primal_solution().size()); + const int actual_iters = + solver_response.get_additional_termination_information().number_of_steps_taken; + CUOPT_DETERMINISM_LOG("LP call %lu result: status=%d iters=%d primal_hash=0x%x", + lp_call_id, + (int)solver_response.get_termination_status(), + actual_iters, + solver_response.get_primal_solution().size() != 0 + ? detail::compute_hash(solver_response.get_primal_solution(), + op_problem.handle_ptr->get_stream()) + : 0u); + + if (determinism_mode && settings.work_context != nullptr) { + double work_to_record = settings.work_limit; + if (estim_iters > 0) { + work_to_record = + settings.work_limit * std::clamp((double)actual_iters / (double)estim_iters, 0.0, 1.0); + } + CUOPT_DETERMINISM_LOG( + "LP call %lu recording %.6fwu (actual_iters=%d estim_iters=%d requested=%.6f)", + lp_call_id, + work_to_record, + actual_iters, + estim_iters, + settings.work_limit); + settings.work_context->record_work_sync_on_horizon(work_to_record); + } if (solver_response.get_primal_solution().size() != 0 && solver_response.get_dual_solution().size() != 0 && settings.save_state) { - CUOPT_LOG_DEBUG("saving initial primal solution of size %d", lp_state.prev_primal.size()); + CUOPT_LOG_TRACE("saving initial primal solution of size %d", lp_state.prev_primal.size()); lp_state.set_state(solver_response.get_primal_solution(), solver_response.get_dual_solution()); } if (solver_response.get_primal_solution().size() != 0) { @@ -101,11 +196,17 @@ optimization_problem_solution_t get_relaxed_lp_solution( solver_response.get_primal_solution().size(), op_problem.handle_ptr->get_stream()); } + CUOPT_DETERMINISM_LOG("LP call %lu assignment_after_copy hash=0x%x", + lp_call_id, + detail::compute_hash(assignment, op_problem.handle_ptr->get_stream())); if (solver_response.get_termination_status() == pdlp_termination_status_t::Optimal) { - CUOPT_LOG_DEBUG("feasible solution found with LP objective %f", + CUOPT_LOG_TRACE("feasible solution found with LP objective %f", solver_response.get_objective_value()); } else { - CUOPT_LOG_DEBUG("LP returned with reason %d", solver_response.get_termination_status()); + CUOPT_DETERMINISM_LOG( + "LP returned with reason %d, %d iterations", + solver_response.get_termination_status(), + solver_response.get_additional_termination_information().number_of_steps_taken); } return solver_response; diff --git a/cpp/src/mip_heuristics/relaxed_lp/relaxed_lp.cuh b/cpp/src/mip_heuristics/relaxed_lp/relaxed_lp.cuh index 9fe5fb9071..06698d79ae 100644 --- a/cpp/src/mip_heuristics/relaxed_lp/relaxed_lp.cuh +++ b/cpp/src/mip_heuristics/relaxed_lp/relaxed_lp.cuh @@ -12,19 +12,23 @@ #include #include #include +#include #include "lp_state.cuh" namespace cuopt::linear_programming::detail { struct relaxed_lp_settings_t { - double tolerance = 1e-4; - double time_limit = 1.0; - bool check_infeasibility = true; - bool return_first_feasible = false; - bool save_state = true; - bool per_constraint_residual = true; - bool has_initial_primal = true; - std::atomic* concurrent_halt = nullptr; + double tolerance = 1e-4; + double time_limit = 1.0; + int iteration_limit = std::numeric_limits::max(); + double work_limit = std::numeric_limits::infinity(); + bool check_infeasibility = true; + bool return_first_feasible = false; + bool save_state = true; + bool per_constraint_residual = true; + bool has_initial_primal = true; + std::atomic* concurrent_halt = nullptr; + cuopt::work_limit_context_t* work_context = nullptr; }; template diff --git a/cpp/src/mip_heuristics/solution/solution.cu b/cpp/src/mip_heuristics/solution/solution.cu index 531d54372c..ed7195663e 100644 --- a/cpp/src/mip_heuristics/solution/solution.cu +++ b/cpp/src/mip_heuristics/solution/solution.cu @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -46,8 +47,6 @@ solution_t::solution_t(problem_t& problem_) assignment(std::move(get_lower_bounds(problem_.variable_bounds, handle_ptr))), lower_excess(problem_.n_constraints, handle_ptr->get_stream()), upper_excess(problem_.n_constraints, handle_ptr->get_stream()), - lower_slack(problem_.n_constraints, handle_ptr->get_stream()), - upper_slack(problem_.n_constraints, handle_ptr->get_stream()), constraint_value(problem_.n_constraints, handle_ptr->get_stream()), obj_val(handle_ptr->get_stream()), n_feasible_constraints(handle_ptr->get_stream()), @@ -56,6 +55,22 @@ solution_t::solution_t(problem_t& problem_) clamp_within_var_bounds(assignment, problem_ptr, handle_ptr); } +template +solution_t::solution_t(problem_t& problem_, + const raft::handle_t* handle_override) + : problem_ptr(&problem_), + handle_ptr(handle_override), + assignment(std::move(get_lower_bounds(problem_.variable_bounds, handle_ptr))), + lower_excess(problem_.n_constraints, handle_ptr->get_stream()), + upper_excess(problem_.n_constraints, handle_ptr->get_stream()), + constraint_value(problem_.n_constraints, handle_ptr->get_stream()), + obj_val(handle_ptr->get_stream()), + n_feasible_constraints(handle_ptr->get_stream()), + lp_state(problem_, handle_ptr->get_stream()) +{ + clamp_within_var_bounds(assignment, problem_ptr, handle_ptr); +} + template solution_t::solution_t(const solution_t& other) : problem_ptr(other.problem_ptr), @@ -63,8 +78,6 @@ solution_t::solution_t(const solution_t& other) assignment(other.assignment, handle_ptr->get_stream()), lower_excess(other.lower_excess, handle_ptr->get_stream()), upper_excess(other.upper_excess, handle_ptr->get_stream()), - lower_slack(other.lower_slack, handle_ptr->get_stream()), - upper_slack(other.upper_slack, handle_ptr->get_stream()), constraint_value(other.constraint_value, handle_ptr->get_stream()), obj_val(other.obj_val, handle_ptr->get_stream()), n_feasible_constraints(other.n_feasible_constraints, handle_ptr->get_stream()), @@ -91,10 +104,18 @@ void solution_t::copy_from(const solution_t& other_sol) h_user_obj = other_sol.h_user_obj; h_infeasibility_cost = other_sol.h_infeasibility_cost; expand_device_copy(assignment, other_sol.assignment, handle_ptr->get_stream()); + + // slack, excess, and constraint value may be uninitialized (and computed later). Mark them as + // such + cuopt::mark_span_as_initialized(make_span(other_sol.lower_excess), handle_ptr->get_stream()); + cuopt::mark_span_as_initialized(make_span(other_sol.upper_excess), handle_ptr->get_stream()); + cuopt::mark_span_as_initialized(make_span(other_sol.constraint_value), handle_ptr->get_stream()); + cuopt::mark_span_as_initialized(make_span(other_sol.obj_val), handle_ptr->get_stream()); + cuopt::mark_span_as_initialized(make_span(other_sol.n_feasible_constraints), + handle_ptr->get_stream()); + expand_device_copy(lower_excess, other_sol.lower_excess, handle_ptr->get_stream()); expand_device_copy(upper_excess, other_sol.upper_excess, handle_ptr->get_stream()); - expand_device_copy(lower_slack, other_sol.lower_slack, handle_ptr->get_stream()); - expand_device_copy(upper_slack, other_sol.upper_slack, handle_ptr->get_stream()); expand_device_copy(constraint_value, other_sol.constraint_value, handle_ptr->get_stream()); raft::copy(obj_val.data(), other_sol.obj_val.data(), 1, handle_ptr->get_stream()); raft::copy(n_feasible_constraints.data(), @@ -113,14 +134,26 @@ void solution_t::copy_from(const solution_t& other_sol) template void solution_t::resize_to_problem() { + i_t old_n_vars = lp_state.prev_primal.size(); + i_t old_n_cstrs = lp_state.prev_dual.size(); assignment.resize(problem_ptr->n_variables, handle_ptr->get_stream()); lower_excess.resize(problem_ptr->n_constraints, handle_ptr->get_stream()); upper_excess.resize(problem_ptr->n_constraints, handle_ptr->get_stream()); - lower_slack.resize(problem_ptr->n_constraints, handle_ptr->get_stream()); - upper_slack.resize(problem_ptr->n_constraints, handle_ptr->get_stream()); constraint_value.resize(problem_ptr->n_constraints, handle_ptr->get_stream()); lp_state.prev_primal.resize(problem_ptr->n_variables, handle_ptr->get_stream()); lp_state.prev_dual.resize(problem_ptr->n_constraints, handle_ptr->get_stream()); + if (problem_ptr->n_variables > old_n_vars) { + thrust::fill(handle_ptr->get_thrust_policy(), + lp_state.prev_primal.data() + old_n_vars, + lp_state.prev_primal.data() + problem_ptr->n_variables, + f_t(0)); + } + if (problem_ptr->n_constraints > old_n_cstrs) { + thrust::fill(handle_ptr->get_thrust_policy(), + lp_state.prev_dual.data() + old_n_cstrs, + lp_state.prev_dual.data() + problem_ptr->n_constraints, + f_t(0)); + } } template @@ -131,10 +164,6 @@ void solution_t::resize_to_original_problem() handle_ptr->get_stream()); upper_excess.resize(problem_ptr->original_problem_ptr->get_n_constraints(), handle_ptr->get_stream()); - lower_slack.resize(problem_ptr->original_problem_ptr->get_n_constraints(), - handle_ptr->get_stream()); - upper_slack.resize(problem_ptr->original_problem_ptr->get_n_constraints(), - handle_ptr->get_stream()); constraint_value.resize(problem_ptr->original_problem_ptr->get_n_constraints(), handle_ptr->get_stream()); lp_state.prev_primal.resize(problem_ptr->original_problem_ptr->get_n_variables(), @@ -149,8 +178,6 @@ void solution_t::resize_copy(const solution_t& other_sol) assignment.resize(other_sol.assignment.size(), handle_ptr->get_stream()); lower_excess.resize(other_sol.lower_excess.size(), handle_ptr->get_stream()); upper_excess.resize(other_sol.upper_excess.size(), handle_ptr->get_stream()); - lower_slack.resize(other_sol.lower_slack.size(), handle_ptr->get_stream()); - upper_slack.resize(other_sol.upper_slack.size(), handle_ptr->get_stream()); constraint_value.resize(other_sol.constraint_value.size(), handle_ptr->get_stream()); lp_state.prev_primal.resize(other_sol.lp_state.prev_primal.size(), handle_ptr->get_stream()); lp_state.prev_dual.resize(other_sol.lp_state.prev_dual.size(), handle_ptr->get_stream()); @@ -165,8 +192,6 @@ typename solution_t::view_t solution_t::view() v.assignment = raft::device_span{assignment.data(), assignment.size()}; v.lower_excess = raft::device_span{lower_excess.data(), lower_excess.size()}; v.upper_excess = raft::device_span{upper_excess.data(), upper_excess.size()}; - v.lower_slack = raft::device_span{lower_slack.data(), lower_slack.size()}; - v.upper_slack = raft::device_span{upper_slack.data(), upper_slack.size()}; v.constraint_value = raft::device_span{constraint_value.data(), constraint_value.size()}; v.obj_val = obj_val.data(); v.n_feasible_constraints = n_feasible_constraints.data(); @@ -626,7 +651,7 @@ mip_solution_t solution_t::get_solution(bool output_feasible "Solution objective: %f , relative_mip_gap %f solution_bound %f presolve_time %f " "total_solve_time %f " "max constraint violation %f max int violation %f max var bounds violation %f " - "nodes %d simplex_iterations %d", + "nodes %d simplex_iterations %d hash %x", h_user_obj, rel_mip_gap, solution_bound, @@ -636,7 +661,8 @@ mip_solution_t solution_t::get_solution(bool output_feasible max_int_violation, max_variable_bound_violation, num_nodes, - num_simplex_iterations); + num_simplex_iterations, + get_hash()); } const bool not_optimal = rel_mip_gap > problem_ptr->tolerances.relative_mip_gap && abs_mip_gap > problem_ptr->tolerances.absolute_mip_gap; @@ -660,6 +686,13 @@ mip_solution_t solution_t::get_solution(bool output_feasible } } +template +uint32_t solution_t::get_hash() const +{ + auto h_assignment = host_copy(assignment, handle_ptr->get_stream()); + return compute_hash(h_assignment); +} + #if MIP_INSTANTIATE_FLOAT || PDLP_INSTANTIATE_FLOAT template class solution_t; #endif diff --git a/cpp/src/mip_heuristics/solution/solution.cuh b/cpp/src/mip_heuristics/solution/solution.cuh index f6c2c2f802..4f6681e5a6 100644 --- a/cpp/src/mip_heuristics/solution/solution.cuh +++ b/cpp/src/mip_heuristics/solution/solution.cuh @@ -25,6 +25,7 @@ template class solution_t { public: solution_t(problem_t& problem); + solution_t(problem_t& problem, const raft::handle_t* handle_override); solution_t(const solution_t& other); solution_t& operator=(solution_t&& other) noexcept = default; solution_t(solution_t&& other) = default; @@ -99,6 +100,7 @@ class solution_t { f_t compute_max_constraint_violation(); f_t compute_max_int_violation(); f_t compute_max_variable_violation(); + uint32_t get_hash() const; struct view_t { // let's not bloat the class for every simple getter and setters @@ -112,8 +114,6 @@ class solution_t { raft::device_span assignment; raft::device_span lower_excess; raft::device_span upper_excess; - raft::device_span lower_slack; - raft::device_span upper_slack; raft::device_span constraint_value; f_t* obj_val; i_t* n_feasible_constraints; @@ -128,8 +128,6 @@ class solution_t { rmm::device_uvector assignment; rmm::device_uvector lower_excess; rmm::device_uvector upper_excess; - rmm::device_uvector lower_slack; - rmm::device_uvector upper_slack; rmm::device_uvector constraint_value; rmm::device_scalar obj_val; rmm::device_scalar n_feasible_constraints; diff --git a/cpp/src/mip_heuristics/solution_callbacks.cuh b/cpp/src/mip_heuristics/solution_callbacks.cuh new file mode 100644 index 0000000000..82db5784a3 --- /dev/null +++ b/cpp/src/mip_heuristics/solution_callbacks.cuh @@ -0,0 +1,206 @@ +/* clang-format off */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* clang-format on */ + +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include + +namespace cuopt::linear_programming::detail { + +template +struct solution_callback_payload_t { + std::vector assignment{}; + f_t user_objective{}; + f_t solver_objective{}; + internals::mip_solution_callback_info_t callback_info{}; +}; + +template +class solution_publication_t { + public: + solution_publication_t(const mip_solver_settings_t& settings_, + solver_stats_t& stats_) + : settings(settings_), stats(stats_) + { + } + + void reset_published_best(f_t objective = std::numeric_limits::max()) + { + best_callback_feasible_objective_ = objective; + } + + solution_callback_payload_t build_callback_payload( + problem_t* problem_ptr, + pdlp_initial_scaling_strategy_t& scaling, + solution_t& sol, + internals::mip_solution_origin_t origin, + double work_timestamp) + { + cuopt_assert(problem_ptr != nullptr, "Callback payload problem pointer must not be null"); + cuopt_assert(work_timestamp >= 0.0, "work_timestamp must not be negative"); + solution_callback_payload_t payload{}; + payload.user_objective = sol.get_user_objective(); + payload.solver_objective = sol.get_objective(); + payload.callback_info.origin = origin; + payload.callback_info.work_timestamp = work_timestamp; + solution_t temp_sol(sol); + problem_ptr->post_process_assignment(temp_sol.assignment, true, sol.handle_ptr); + if (settings.mip_scaling) { + rmm::device_uvector dummy(0, temp_sol.handle_ptr->get_stream()); + scaling.unscale_solutions(temp_sol.assignment, dummy); + } + if (problem_ptr->has_papilo_presolve_data()) { + problem_ptr->papilo_uncrush_assignment(temp_sol.assignment, sol.handle_ptr); + } + payload.assignment = temp_sol.get_host_assignment(); + return payload; + } + + bool publish_new_best_feasible(const solution_callback_payload_t& payload, + double elapsed_time = -1.0) + { + std::lock_guard lock(solution_callback_mutex_); + cuopt_assert(std::isfinite(payload.solver_objective), + "Feasible incumbent objective must be finite"); + if (!(payload.solver_objective < best_callback_feasible_objective_)) { return false; } + + if (settings.benchmark_info_ptr != nullptr && elapsed_time >= 0.0) { + settings.benchmark_info_ptr->last_improvement_of_best_feasible = elapsed_time; + } + invoke_get_solution_callbacks(payload); + best_callback_feasible_objective_ = payload.solver_objective; + return true; + } + + void publish_terminal_solution(const solution_callback_payload_t& payload) + { + std::lock_guard lock(solution_callback_mutex_); + invoke_get_solution_callbacks(payload); + best_callback_feasible_objective_ = payload.solver_objective; + } + + private: + void invoke_get_solution_callbacks(const solution_callback_payload_t& payload) + { + auto user_callbacks = settings.get_mip_callbacks(); + CUOPT_LOG_DEBUG("Publishing incumbent: obj=%g wut=%.6f origin=%s callbacks=%zu", + payload.user_objective, + payload.callback_info.work_timestamp, + internals::mip_solution_origin_to_string(payload.callback_info.origin), + user_callbacks.size()); + + std::vector user_objective_vec(1); + std::vector user_bound_vec(1); + user_objective_vec[0] = payload.user_objective; + user_bound_vec[0] = stats.get_solution_bound(); + + for (auto callback : user_callbacks) { + if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION_EXT) { + auto get_sol_callback_ext = static_cast(callback); + get_sol_callback_ext->get_solution(const_cast(payload.assignment.data()), + user_objective_vec.data(), + user_bound_vec.data(), + &payload.callback_info, + get_sol_callback_ext->get_user_data()); + } else if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) { + auto get_sol_callback = static_cast(callback); + get_sol_callback->get_solution(const_cast(payload.assignment.data()), + user_objective_vec.data(), + user_bound_vec.data(), + get_sol_callback->get_user_data()); + } + } + } + + const mip_solver_settings_t& settings; + solver_stats_t& stats; + std::mutex solution_callback_mutex_; + f_t best_callback_feasible_objective_{std::numeric_limits::max()}; +}; + +// Processes SET_SOLUTION user callbacks: invokes the callback, validates/scales/preprocesses +// the returned assignment, and returns it for the caller to reinject. +template +class solution_injection_t { + public: + solution_injection_t(const mip_solver_settings_t& settings_, + solver_stats_t& stats_) + : settings(settings_), stats(stats_) + { + } + + template + void invoke_set_solution_callbacks(problem_t* problem_ptr, + pdlp_initial_scaling_strategy_t& scaling, + solution_t& current_incumbent, + OnInjectedFn&& on_injected) + { + auto user_callbacks = settings.get_mip_callbacks(); + for (auto callback : user_callbacks) { + if (callback->get_type() != internals::base_solution_callback_type::SET_SOLUTION) { + continue; + } + auto set_sol_callback = static_cast(callback); + f_t user_bound = stats.get_solution_bound(); + auto callback_num_variables = problem_ptr->original_problem_ptr->get_n_variables(); + rmm::device_uvector incumbent_assignment(callback_num_variables, + current_incumbent.handle_ptr->get_stream()); + auto inf = std::numeric_limits::infinity(); + current_incumbent.handle_ptr->sync_stream(); + std::vector h_incumbent_assignment(incumbent_assignment.size()); + std::vector h_outside_sol_objective(1, inf); + std::vector h_user_bound(1, user_bound); + set_sol_callback->set_solution(h_incumbent_assignment.data(), + h_outside_sol_objective.data(), + h_user_bound.data(), + set_sol_callback->get_user_data()); + f_t outside_sol_objective = h_outside_sol_objective[0]; + if (outside_sol_objective == inf) { continue; } + + raft::copy(incumbent_assignment.data(), + h_incumbent_assignment.data(), + incumbent_assignment.size(), + current_incumbent.handle_ptr->get_stream()); + if (settings.mip_scaling) { scaling.scale_solutions(incumbent_assignment); } + bool is_valid = problem_ptr->pre_process_assignment(incumbent_assignment); + if (!is_valid) { continue; } + + solution_t outside_sol(current_incumbent); + cuopt_assert(outside_sol.assignment.size() == incumbent_assignment.size(), + "Incumbent assignment size mismatch"); + raft::copy(outside_sol.assignment.data(), + incumbent_assignment.data(), + incumbent_assignment.size(), + current_incumbent.handle_ptr->get_stream()); + outside_sol.compute_feasibility(); + + CUOPT_LOG_DEBUG("Injected solution feasibility = %d objective = %g excess = %g", + outside_sol.get_feasible(), + outside_sol.get_user_objective(), + outside_sol.get_total_excess()); + cuopt_assert(std::abs(outside_sol.get_user_objective() - outside_sol_objective) <= 1e-6, + "External solution objective mismatch"); + on_injected(outside_sol.get_host_assignment(), + outside_sol.get_objective(), + internals::mip_solution_origin_t::USER_INITIAL); + } + } + + private: + const mip_solver_settings_t& settings; + solver_stats_t& stats; +}; + +} // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip_heuristics/solve.cu b/cpp/src/mip_heuristics/solve.cu index f5a2172f2e..b783281d61 100644 --- a/cpp/src/mip_heuristics/solve.cu +++ b/cpp/src/mip_heuristics/solve.cu @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,7 @@ #include #include #include +#include #include #include @@ -61,7 +63,7 @@ static void init_handler(const raft::handle_t* handle_ptr) template mip_solution_t run_mip(detail::problem_t& problem, mip_solver_settings_t const& settings, - timer_t& timer) + cuopt::termination_checker_t& timer) { raft::common::nvtx::range fun_scope("run_mip"); auto constexpr const running_mip = true; @@ -95,30 +97,18 @@ mip_solution_t run_mip(detail::problem_t& problem, solution.compute_objective(); // just to ensure h_user_obj is set auto stats = solver_stats_t{}; stats.set_solution_bound(solution.get_user_objective()); - // log the objective for scripts which need it CUOPT_LOG_INFO("Best feasible: %f", solution.get_user_objective()); - for (auto callback : settings.get_mip_callbacks()) { - if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) { - auto temp_sol(solution); - auto get_sol_callback = static_cast(callback); - std::vector user_objective_vec(1); - std::vector user_bound_vec(1); - user_objective_vec[0] = solution.get_user_objective(); - user_bound_vec[0] = stats.get_solution_bound(); - if (problem.has_papilo_presolve_data()) { - problem.papilo_uncrush_assignment(temp_sol.assignment); - } - std::vector user_assignment_vec(temp_sol.assignment.size()); - raft::copy(user_assignment_vec.data(), - temp_sol.assignment.data(), - temp_sol.assignment.size(), - temp_sol.handle_ptr->get_stream()); - solution.handle_ptr->sync_stream(); - get_sol_callback->get_solution(user_assignment_vec.data(), - user_objective_vec.data(), - user_bound_vec.data(), - get_sol_callback->get_user_data()); + { + detail::solution_callback_payload_t payload{}; + payload.user_objective = solution.get_user_objective(); + payload.solver_objective = solution.get_objective(); + detail::solution_t temp_sol(solution); + if (problem.has_papilo_presolve_data()) { + problem.papilo_uncrush_assignment(temp_sol.assignment); } + payload.assignment = temp_sol.get_host_assignment(); + detail::solution_publication_t pub(settings, stats); + pub.publish_terminal_solution(payload); } return solution.get_solution(true, stats, false); } @@ -218,6 +208,15 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, // Initialize seed generator if a specific seed is requested if (settings.seed >= 0) { cuopt::seed_generator::set_seed(settings.seed); } + CUOPT_DETERMINISM_LOG( + "Deterministic solve start settings: seed=%lld seed_state=%lld det_mode=%d " + "work_limit=%.6f max_cut_passes=%d num_cpu_threads=%d", + (long long)settings.seed, + (long long)cuopt::seed_generator::peek_seed(), + (int)settings.determinism_mode, + (double)settings.work_limit, + settings.max_cut_passes, + settings.num_cpu_threads); raft::common::nvtx::range fun_scope("Running solver"); @@ -240,13 +239,14 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, op_problem.get_handle_ptr()->get_stream()); } - auto timer = timer_t(time_limit); + auto timer = + cuopt::termination_checker_t(time_limit, cuopt::termination_checker_t::root_tag_t{}); + const bool deterministic_run = (settings.determinism_mode != CUOPT_DETERMINISM_NONE); double presolve_time = 0.0; std::unique_ptr> presolver; std::optional> presolve_result; - detail::problem_t problem( - op_problem, settings.get_tolerances(), settings.determinism_mode == CUOPT_MODE_DETERMINISTIC); + detail::problem_t problem(op_problem, settings.get_tolerances(), deterministic_run); auto run_presolve = settings.presolver != presolver_t::None; run_presolve = run_presolve && settings.initial_solutions.size() == 0; @@ -271,10 +271,9 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, // allocate not more than 10% of the time limit to presolve. // Note that this is not the presolve time, but the time limit for presolve. double presolve_time_limit = std::min(0.1 * time_limit, 60.0); - if (settings.determinism_mode == CUOPT_MODE_DETERMINISTIC) { - presolve_time_limit = std::numeric_limits::infinity(); - } - presolver = std::make_unique>(); + if (deterministic_run) { presolve_time_limit = std::numeric_limits::infinity(); } + presolver = std::make_unique>(); + presolver->set_deterministic(deterministic_run); auto result = presolver->apply(op_problem, cuopt::linear_programming::problem_category_t::MIP, settings.presolver, @@ -290,7 +289,8 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, } presolve_result.emplace(std::move(*result)); - problem = detail::problem_t(presolve_result->reduced_problem); + problem = detail::problem_t( + presolve_result->reduced_problem, settings.get_tolerances(), deterministic_run); problem.set_papilo_presolve_data(presolver.get(), presolve_result->reduced_to_original_map, presolve_result->original_to_reduced_map, @@ -333,7 +333,8 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, reduced_costs.data(), reduced_costs.data() + reduced_costs.size(), std::numeric_limits::signaling_NaN()); - detail::problem_t full_problem(op_problem); + detail::problem_t full_problem( + op_problem, settings.get_tolerances(), deterministic_run); detail::solution_t full_sol(full_problem); full_sol.copy_new_assignment( cuopt::host_copy(primal_solution, op_problem.get_handle_ptr()->get_stream())); diff --git a/cpp/src/mip_heuristics/solver.cu b/cpp/src/mip_heuristics/solver.cu index f25c093afb..fe9f27bb30 100644 --- a/cpp/src/mip_heuristics/solver.cu +++ b/cpp/src/mip_heuristics/solver.cu @@ -17,6 +17,10 @@ #include #include #include +#include + +#undef CUOPT_DETERMINISM_LOG +#define CUOPT_DETERMINISM_LOG(...) CUOPT_LOG_INFO(__VA_ARGS__) #include #include @@ -42,7 +46,7 @@ template mip_solver_t::mip_solver_t(const problem_t& op_problem, const mip_solver_settings_t& solver_settings, pdlp_initial_scaling_strategy_t& scaling, - timer_t timer) + cuopt::termination_checker_t& timer) : op_problem_(op_problem), solver_settings_(solver_settings), context(op_problem.handle_ptr, @@ -51,19 +55,36 @@ mip_solver_t::mip_solver_t(const problem_t& op_problem, scaling), timer_(timer) { + context.termination = &timer_; init_handler(op_problem.handle_ptr); } template -struct branch_and_bound_solution_helper_t { - branch_and_bound_solution_helper_t(diversity_manager_t* dm, - dual_simplex::simplex_solver_settings_t& settings) - : dm(dm), settings_(settings) {}; - - void solution_callback(std::vector& solution, f_t objective) +struct bb_observer_adapter_t { + bb_observer_adapter_t(mip_solver_context_t* context, diversity_manager_t* dm) + : context(context), dm(dm) {}; + + void new_incumbent_callback(std::vector& solution, + f_t objective, + const internals::mip_solution_callback_info_t& info, + double work_timestamp) { - dm->population.add_external_solution(solution, objective, solution_origin_t::BRANCH_AND_BOUND); - dm->rins.new_best_incumbent_callback(solution); + if (context->settings.determinism_mode & CUOPT_DETERMINISM_BB) { + // B&B calls this from its own thread. Use a dedicated per-thread stream + // to avoid racing on the heuristic thread's stream. + raft::handle_t callback_handle(rmm::cuda_stream_per_thread); + solution_t temp_sol(*context->problem_ptr, &callback_handle); + temp_sol.copy_new_assignment(solution); + temp_sol.compute_feasibility(); + const auto payload = context->solution_publication.build_callback_payload( + context->problem_ptr, context->scaling, temp_sol, info.origin, work_timestamp); + context->solution_publication.publish_new_best_feasible(payload, dm->timer.elapsed_time()); + } + if (context->diversity_manager_ptr != nullptr) { + context->diversity_manager_ptr->population.add_external_solution( + solution, objective, internals::mip_solution_origin_t::BRANCH_AND_BOUND_NODE); + context->diversity_manager_ptr->rins.new_best_incumbent_callback(solution); + } } void set_simplex_solution(std::vector& solution, @@ -79,8 +100,8 @@ struct branch_and_bound_solution_helper_t { } void preempt_heuristic_solver() { dm->population.preempt_heuristic_solver(); } + mip_solver_context_t* context; diversity_manager_t* dm; - dual_simplex::simplex_solver_settings_t& settings_; }; template @@ -98,25 +119,27 @@ solution_t mip_solver_t::run_solver() CUOPT_LOG_INFO("Problem fully reduced in presolve"); solution_t sol(*context.problem_ptr); sol.set_problem_fully_reduced(); - for (auto callback : context.settings.get_mip_callbacks()) { - if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) { - auto get_sol_callback = static_cast(callback); - dm.population.invoke_get_solution_callback(sol, get_sol_callback); - } - } + const auto payload = context.solution_publication.build_callback_payload( + context.problem_ptr, context.scaling, sol, internals::mip_solution_origin_t::PRESOLVE, 0.0); + context.solution_publication.publish_terminal_solution(payload); context.problem_ptr->post_process_solution(sol); return sol; } - dm.timer = timer_; - const bool run_presolve = context.settings.presolver != presolver_t::None; - f_t time_limit = context.settings.determinism_mode == CUOPT_MODE_DETERMINISTIC - ? std::numeric_limits::infinity() - : timer_.remaining_time(); + const bool deterministic_run = + (context.settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS); + const f_t gpu_heur_work_limit = + deterministic_run ? context.settings.work_limit : timer_.get_time_limit(); + if (deterministic_run) + cuopt_assert(gpu_heur_work_limit >= 0.0, + "Deterministic GPU heuristic work limit must be non-negative"); + dm.timer = cuopt::termination_checker_t(context.gpu_heur_loop, gpu_heur_work_limit, timer_); + const bool run_presolve = context.settings.presolver != presolver_t::None; + f_t time_limit = + deterministic_run ? std::numeric_limits::infinity() : timer_.remaining_time(); double presolve_time_limit = std::min(0.1 * time_limit, 60.0); - presolve_time_limit = context.settings.determinism_mode == CUOPT_MODE_DETERMINISTIC - ? std::numeric_limits::infinity() - : presolve_time_limit; - bool presolve_success = run_presolve ? dm.run_presolve(presolve_time_limit, timer_) : true; + presolve_time_limit = + deterministic_run ? std::numeric_limits::infinity() : presolve_time_limit; + bool presolve_success = run_presolve ? dm.run_presolve(presolve_time_limit, timer_) : true; if (!presolve_success) { CUOPT_LOG_INFO("Problem proven infeasible in presolve"); solution_t sol(*context.problem_ptr); @@ -128,12 +151,9 @@ solution_t mip_solver_t::run_solver() CUOPT_LOG_INFO("Problem full reduced in presolve"); solution_t sol(*context.problem_ptr); sol.set_problem_fully_reduced(); - for (auto callback : context.settings.get_mip_callbacks()) { - if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) { - auto get_sol_callback = static_cast(callback); - dm.population.invoke_get_solution_callback(sol, get_sol_callback); - } - } + const auto payload = context.solution_publication.build_callback_payload( + context.problem_ptr, context.scaling, sol, internals::mip_solution_origin_t::PRESOLVE, 0.0); + context.solution_publication.publish_terminal_solution(payload); context.problem_ptr->post_process_solution(sol); return sol; } @@ -166,12 +186,9 @@ solution_t mip_solver_t::run_solver() sol.set_problem_fully_reduced(); } if (opt_sol.get_termination_status() == pdlp_termination_status_t::Optimal) { - for (auto callback : context.settings.get_mip_callbacks()) { - if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) { - auto get_sol_callback = static_cast(callback); - dm.population.invoke_get_solution_callback(sol, get_sol_callback); - } - } + const auto payload = context.solution_publication.build_callback_payload( + context.problem_ptr, context.scaling, sol, internals::mip_solution_origin_t::PRESOLVE, 0.0); + context.solution_publication.publish_terminal_solution(payload); } context.problem_ptr->post_process_solution(sol); return sol; @@ -189,7 +206,7 @@ solution_t mip_solver_t::run_solver() branch_and_bound_problem.objective_is_integral = context.problem_ptr->is_objective_integral(); dual_simplex::simplex_solver_settings_t branch_and_bound_settings; std::unique_ptr> branch_and_bound; - branch_and_bound_solution_helper_t solution_helper(&dm, branch_and_bound_settings); + bb_observer_adapter_t solution_helper(&context, &dm); dual_simplex::mip_solution_t branch_and_bound_solution(1); bool run_bb = !context.settings.heuristics_only; @@ -210,9 +227,9 @@ solution_t mip_solver_t::run_solver() branch_and_bound_settings.max_cut_passes = context.settings.max_cut_passes; branch_and_bound_settings.mir_cuts = context.settings.mir_cuts; branch_and_bound_settings.deterministic = - context.settings.determinism_mode == CUOPT_MODE_DETERMINISTIC; + (context.settings.determinism_mode & CUOPT_DETERMINISM_BB); - if (context.settings.determinism_mode == CUOPT_MODE_DETERMINISTIC) { + if ((context.settings.determinism_mode & CUOPT_DETERMINISM_BB)) { branch_and_bound_settings.work_limit = context.settings.work_limit; } else { branch_and_bound_settings.work_limit = std::numeric_limits::infinity(); @@ -233,32 +250,36 @@ solution_t mip_solver_t::run_solver() context.settings.reduced_cost_strengthening == -1 ? 2 : context.settings.reduced_cost_strengthening; + branch_and_bound_settings.bnb_work_unit_scale = solver_settings_.bnb_work_unit_scale; + branch_and_bound_settings.gpu_heur_wait_for_exploration = + solver_settings_.gpu_heur_wait_for_exploration; if (context.settings.num_cpu_threads < 0) { branch_and_bound_settings.num_threads = std::max(1, omp_get_max_threads() - 1); } else { branch_and_bound_settings.num_threads = std::max(1, context.settings.num_cpu_threads); } + CUOPT_LOG_INFO("Using %d CPU threads for B&B", branch_and_bound_settings.num_threads); - // Set the branch and bound -> primal heuristics callback - branch_and_bound_settings.solution_callback = - std::bind(&branch_and_bound_solution_helper_t::solution_callback, + branch_and_bound_settings.new_incumbent_callback = + std::bind(&bb_observer_adapter_t::new_incumbent_callback, &solution_helper, std::placeholders::_1, - std::placeholders::_2); - // heuristic_preemption_callback is needed in both modes to properly stop the heuristic thread - branch_and_bound_settings.heuristic_preemption_callback = std::bind( - &branch_and_bound_solution_helper_t::preempt_heuristic_solver, &solution_helper); - if (context.settings.determinism_mode == CUOPT_MODE_OPPORTUNISTIC) { + std::placeholders::_2, + std::placeholders::_3, + std::placeholders::_4); + branch_and_bound_settings.heuristic_preemption_callback = + std::bind(&bb_observer_adapter_t::preempt_heuristic_solver, &solution_helper); + if (!(context.settings.determinism_mode & CUOPT_DETERMINISM_BB)) { branch_and_bound_settings.set_simplex_solution_callback = - std::bind(&branch_and_bound_solution_helper_t::set_simplex_solution, + std::bind(&bb_observer_adapter_t::set_simplex_solution, &solution_helper, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); branch_and_bound_settings.node_processed_callback = - std::bind(&branch_and_bound_solution_helper_t::node_processed_callback, + std::bind(&bb_observer_adapter_t::node_processed_callback, &solution_helper, std::placeholders::_1, std::placeholders::_2); @@ -276,14 +297,15 @@ solution_t mip_solver_t::run_solver() [stats_ptr](f_t user_bound) { stats_ptr->set_solution_bound(user_bound); }); // Set the primal heuristics -> branch and bound callback - if (context.settings.determinism_mode == CUOPT_MODE_OPPORTUNISTIC) { + if (!(context.settings.determinism_mode & CUOPT_DETERMINISM_BB)) { branch_and_bound->set_concurrent_lp_root_solve(true); context.problem_ptr->branch_and_bound_callback = std::bind(&dual_simplex::branch_and_bound_t::set_new_solution, branch_and_bound.get(), - std::placeholders::_1); - } else if (context.settings.determinism_mode == CUOPT_MODE_DETERMINISTIC) { + std::placeholders::_1, + std::placeholders::_2); + } else if ((context.settings.determinism_mode & CUOPT_DETERMINISM_BB)) { branch_and_bound->set_concurrent_lp_root_solve(false); // TODO once deterministic GPU heuristics are integrated // context.problem_ptr->branch_and_bound_callback = @@ -293,17 +315,20 @@ solution_t mip_solver_t::run_solver() } context.work_unit_scheduler_.register_context(branch_and_bound->get_work_unit_context()); - // context.work_unit_scheduler_.verbose = true; - context.problem_ptr->set_root_relaxation_solution_callback = - std::bind(&dual_simplex::branch_and_bound_t::set_root_relaxation_solution, - branch_and_bound.get(), - std::placeholders::_1, - std::placeholders::_2, - std::placeholders::_3, - std::placeholders::_4, - std::placeholders::_5, - std::placeholders::_6); + if ((context.settings.determinism_mode & CUOPT_DETERMINISM_BB)) { + context.problem_ptr->set_root_relaxation_solution_callback = nullptr; + } else { + context.problem_ptr->set_root_relaxation_solution_callback = + std::bind(&dual_simplex::branch_and_bound_t::set_root_relaxation_solution, + branch_and_bound.get(), + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3, + std::placeholders::_4, + std::placeholders::_5, + std::placeholders::_6); + } if (timer_.check_time_limit()) { CUOPT_LOG_INFO("Time limit reached during B&B setup"); @@ -317,10 +342,12 @@ solution_t mip_solver_t::run_solver() // std::async and std::future allow us to get the return value of bb::solve() // without having to manually manage the thread // std::future.get() performs a join() operation to wait until the return status is available - branch_and_bound_status_future = std::async(std::launch::async, - &dual_simplex::branch_and_bound_t::solve, - branch_and_bound.get(), - std::ref(branch_and_bound_solution)); + int bb_device_id = context.handle_ptr->get_device(); + branch_and_bound_status_future = + std::async(std::launch::async, [&branch_and_bound, &branch_and_bound_solution, bb_device_id] { + RAFT_CUDA_TRY(cudaSetDevice(bb_device_id)); + return branch_and_bound->solve(branch_and_bound_solution); + }); } // Start the primal heuristics @@ -333,9 +360,37 @@ solution_t mip_solver_t::run_solver() context.stats.set_solution_bound( context.problem_ptr->get_user_obj_from_solver_obj(branch_and_bound_solution.lower_bound)); } + if ((context.settings.determinism_mode & CUOPT_DETERMINISM_BB) && + std::isfinite(branch_and_bound_solution.objective)) { + solution_t bb_sol(*context.problem_ptr); + bb_sol.copy_new_assignment(branch_and_bound_solution.x); + bool bb_feasible = bb_sol.compute_feasibility(); + if (bb_feasible) { sol = std::move(bb_sol); } + } else if ((context.settings.determinism_mode & CUOPT_DETERMINISM_BB)) { + // In deterministic mode, only solutions formally retired by B&B are valid output. + // Discard the GPU heuristic incumbent that B&B never processed. + sol = solution_t(*context.problem_ptr); + } if (bb_status == dual_simplex::mip_status_t::INFEASIBLE) { sol.set_problem_fully_reduced(); } context.stats.num_nodes = branch_and_bound_solution.nodes_explored; context.stats.num_simplex_iterations = branch_and_bound_solution.simplex_iterations; + + if ((context.settings.determinism_mode & CUOPT_DETERMINISM_BB)) { + double bnb_work = branch_and_bound->get_work_unit_context().current_work(); + double gpu_work = context.gpu_heur_loop.current_work(); + double bnb_scale = solver_settings_.bnb_work_unit_scale; + double gpu_scale = solver_settings_.gpu_heur_work_unit_scale; + CUOPT_LOG_INFO( + "Work unit summary: B&B=%.2f (scale=%.3f, raw=%.2f) GPU_heur=%.2f (scale=%.3f, raw=%.2f) " + "ratio=%.2fx", + bnb_work, + bnb_scale, + bnb_scale > 0 ? bnb_work / bnb_scale : 0.0, + gpu_work, + gpu_scale, + gpu_scale > 0 ? gpu_work / gpu_scale : 0.0, + gpu_work > 0 ? bnb_work / gpu_work : 0.0); + } } sol.compute_feasibility(); rmm::device_scalar is_feasible(sol.handle_ptr->get_stream()); diff --git a/cpp/src/mip_heuristics/solver.cuh b/cpp/src/mip_heuristics/solver.cuh index 1b5fe17244..177fe6e14a 100644 --- a/cpp/src/mip_heuristics/solver.cuh +++ b/cpp/src/mip_heuristics/solver.cuh @@ -10,7 +10,7 @@ #include #include #include -#include +#include #pragma once namespace cuopt::linear_programming::detail { @@ -21,7 +21,7 @@ class mip_solver_t { explicit mip_solver_t(const problem_t& op_problem, const mip_solver_settings_t& solver_settings, pdlp_initial_scaling_strategy_t& scaling, - timer_t timer); + cuopt::termination_checker_t& timer); solution_t run_solver(); solver_stats_t& get_solver_stats() { return context.stats; } @@ -30,7 +30,7 @@ class mip_solver_t { // reference to the original problem const problem_t& op_problem_; const mip_solver_settings_t& solver_settings_; - timer_t timer_; + cuopt::termination_checker_t& timer_; }; } // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip_heuristics/solver_context.cuh b/cpp/src/mip_heuristics/solver_context.cuh index baac1dd9d6..5c66017f04 100644 --- a/cpp/src/mip_heuristics/solver_context.cuh +++ b/cpp/src/mip_heuristics/solver_context.cuh @@ -5,17 +5,18 @@ */ /* clang-format on */ +#pragma once + #include -#include -#include -#include +#include +#include #include #include -#include - -#pragma once +#include +#include +#include // Forward declare namespace cuopt::linear_programming::dual_simplex { @@ -25,6 +26,9 @@ class branch_and_bound_t; namespace cuopt::linear_programming::detail { +struct mip_solver_work_unit_predictors_t { + work_unit_predictor_t fj_predictor{}; +}; template class diversity_manager_t; @@ -36,12 +40,21 @@ struct mip_solver_context_t { problem_t* problem_ptr_, mip_solver_settings_t settings_, pdlp_initial_scaling_strategy_t& scaling) - : handle_ptr(handle_ptr_), problem_ptr(problem_ptr_), settings(settings_), scaling(scaling) + : handle_ptr(handle_ptr_), + problem_ptr(problem_ptr_), + settings(settings_), + scaling(scaling), + solution_publication(settings, stats), + solution_injection(settings, stats) { cuopt_assert(problem_ptr != nullptr, "problem_ptr is nullptr"); stats.set_solution_bound(problem_ptr->maximize ? std::numeric_limits::infinity() : -std::numeric_limits::infinity()); - gpu_heur_loop.deterministic = settings.determinism_mode == CUOPT_MODE_DETERMINISTIC; + gpu_heur_loop.deterministic = (settings.determinism_mode & CUOPT_DETERMINISM_GPU_HEURISTICS); + cuopt_assert(settings.cpufj_work_unit_scale > 0.0, "CPUFJ work-unit scale must be positive"); + cuopt_assert(settings.gpu_heur_work_unit_scale > 0.0, + "GPU heuristic work-unit scale must be positive"); + gpu_heur_loop.work_unit_scale = settings.gpu_heur_work_unit_scale; } mip_solver_context_t(const mip_solver_context_t&) = delete; @@ -55,11 +68,15 @@ struct mip_solver_context_t { const mip_solver_settings_t settings; pdlp_initial_scaling_strategy_t& scaling; solver_stats_t stats; - // Work limit context for tracking work units in deterministic mode (shared across all timers in - // GPU heuristic loop) + mip_solver_work_unit_predictors_t work_unit_predictors; work_limit_context_t gpu_heur_loop{"GPUHeur"}; + solution_publication_t solution_publication; + solution_injection_t solution_injection; + + // Root termination checker — set by mip_solver_t after construction. + // All sub-timers should use this as parent for wall-clock safety. + cuopt::termination_checker_t* termination{nullptr}; - // synchronization every 5 seconds for deterministic mode work_unit_scheduler_t work_unit_scheduler_{5.0}; }; diff --git a/cpp/src/pdlp/cuopt_c.cpp b/cpp/src/pdlp/cuopt_c.cpp index ed2eab02f2..0c9736646f 100644 --- a/cpp/src/pdlp/cuopt_c.cpp +++ b/cpp/src/pdlp/cuopt_c.cpp @@ -49,6 +49,40 @@ class c_get_solution_callback_t : public cuopt::internals::get_solution_callback cuOptMIPGetSolutionCallback callback_; }; +class c_get_solution_callback_ext_t : public cuopt::internals::get_solution_callback_ext_t { + public: + explicit c_get_solution_callback_ext_t(cuOptMIPGetSolutionCallbackExt callback) + : callback_(callback) + { + } + + void get_solution(void* data, + void* objective_value, + void* solution_bound, + const cuopt::internals::mip_solution_callback_info_t* callback_info, + void* user_data) override + { + if (callback_ == nullptr) { return; } + cuOptMIPSolutionCallbackInfo c_callback_info{}; + c_callback_info.struct_size = sizeof(cuOptMIPSolutionCallbackInfo); + if (callback_info != nullptr) { + c_callback_info.origin = (uint32_t)callback_info->origin; + c_callback_info.work_timestamp = callback_info->work_timestamp; + } else { + c_callback_info.origin = CUOPT_MIP_SOLUTION_ORIGIN_UNKNOWN; + c_callback_info.work_timestamp = -1.0; + } + callback_(static_cast(data), + static_cast(objective_value), + static_cast(solution_bound), + &c_callback_info, + user_data); + } + + private: + cuOptMIPGetSolutionCallbackExt callback_; +}; + class c_set_solution_callback_t : public cuopt::internals::set_solution_callback_t { public: explicit c_set_solution_callback_t(cuOptMIPSetSolutionCallback callback) : callback_(callback) {} @@ -767,6 +801,19 @@ cuopt_int_t cuOptSetMIPGetSolutionCallback(cuOptSolverSettings settings, return CUOPT_SUCCESS; } +cuopt_int_t cuOptSetMIPGetSolutionCallbackExt(cuOptSolverSettings settings, + cuOptMIPGetSolutionCallbackExt callback, + void* user_data) +{ + if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; } + if (callback == nullptr) { return CUOPT_INVALID_ARGUMENT; } + solver_settings_handle_t* settings_handle = get_settings_handle(settings); + auto callback_wrapper = std::make_unique(callback); + settings_handle->settings->set_mip_callback(callback_wrapper.get(), user_data); + settings_handle->callbacks.push_back(std::move(callback_wrapper)); + return CUOPT_SUCCESS; +} + cuopt_int_t cuOptSetMIPSetSolutionCallback(cuOptSolverSettings settings, cuOptMIPSetSolutionCallback callback, void* user_data) diff --git a/cpp/src/pdlp/pdlp.cu b/cpp/src/pdlp/pdlp.cu index 82e79098a7..a6f16ec972 100644 --- a/cpp/src/pdlp/pdlp.cu +++ b/cpp/src/pdlp/pdlp.cu @@ -2257,6 +2257,10 @@ optimization_problem_solution_t pdlp_solver_t::run_solver(co cuopt_expects(!batch_mode_, cuopt::error_type_t::ValidationError, "Restart to average not supported in batch mode"); + raft::copy(unscaled_primal_avg_solution_.data(), + pdhg_solver_.get_primal_solution().data(), + primal_size_h_, + stream_view_); cub::DeviceTransform::Transform( cuda::std::make_tuple(unscaled_primal_avg_solution_.data(), op_problem_scaled_.variable_bounds.data()), diff --git a/cpp/src/utilities/copy_helpers.hpp b/cpp/src/utilities/copy_helpers.hpp index 36a4659059..3558d31ea5 100644 --- a/cpp/src/utilities/copy_helpers.hpp +++ b/cpp/src/utilities/copy_helpers.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -335,6 +336,17 @@ raft::device_span make_span(rmm::device_uvector const& container) return raft::device_span(container.data(), container.size()); } +template +raft::device_span make_span(rmm::device_scalar& scalar) +{ + return raft::device_span(scalar.data(), 1); +} + +template +raft::device_span make_span(rmm::device_scalar const& scalar) +{ + return raft::device_span(scalar.data(), 1); +} // resizes the device vector if it the std vector is larger template inline void expand_device_copy(rmm::device_uvector& device_vec, diff --git a/cpp/src/utilities/cuda_helpers.cuh b/cpp/src/utilities/cuda_helpers.cuh index 946099648d..7c591624d2 100644 --- a/cpp/src/utilities/cuda_helpers.cuh +++ b/cpp/src/utilities/cuda_helpers.cuh @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,17 @@ #include #include +#if CUDART_VERSION >= 12080 +// TODO: investigate why this is necessary? dependency conflict? file NVBUG if necessary +#include +#ifndef NVTX_NULLPTR +#define NVTX_NULLPTR nullptr +#endif +#ifndef NVTX_REINTERPRET_CAST +#define NVTX_REINTERPRET_CAST(type, value) (reinterpret_cast(value)) +#endif +#include +#endif namespace cuopt { #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < 700) @@ -237,4 +249,48 @@ inline size_t get_device_memory_size() } } +// NOTE: this marks a range of virtual memory as initialized. This is not tied to any object's +// lifetime As such, when using a pool for allocations, false negatives could occurs e.g. a range +// previously marked as initialized is now occupied by a new uninitialized object Unlikely to cause +// issues in practice - but worth noting (RAII? I'm not even sure the API allows to un-mark a range +// as initialized) +static inline void mark_memory_as_initialized(const void* ptr, size_t size, cudaStream_t stream = 0) +{ +#if CUDART_VERSION >= 12080 + + if (size == 0 || ptr == nullptr) return; + +#if defined(CUDA_API_PER_THREAD_DEFAULT_STREAM) + constexpr auto PerThreadDefaultStream = true; +#else + constexpr auto PerThreadDefaultStream = false; +#endif + + nvtxMemVirtualRangeDesc_t nvtxRangeDesc = {}; + nvtxRangeDesc.size = size; + nvtxRangeDesc.ptr = ptr; + + nvtxMemMarkInitializedBatch_t nvtxRegionsDesc = {}; + nvtxRegionsDesc.extCompatID = NVTX_EXT_COMPATID_MEM; + nvtxRegionsDesc.structSize = sizeof(nvtxRegionsDesc); + nvtxRegionsDesc.regionType = NVTX_MEM_TYPE_VIRTUAL_ADDRESS; + nvtxRegionsDesc.regionDescCount = 1; + nvtxRegionsDesc.regionDescElementSize = sizeof(nvtxRangeDesc); + nvtxRegionsDesc.regionDescElements = &nvtxRangeDesc; + + nvtxMemCudaMarkInitialized( + raft::common::nvtx::detail::domain_store::value(), + stream, + PerThreadDefaultStream, + &nvtxRegionsDesc); +#endif +} + +template +static inline void mark_span_as_initialized(const raft::device_span span, + rmm::cuda_stream_view stream) +{ + mark_memory_as_initialized(span.data(), span.size() * sizeof(T), stream.value()); +} + } // namespace cuopt diff --git a/cpp/src/utilities/determinism_log.hpp b/cpp/src/utilities/determinism_log.hpp new file mode 100644 index 0000000000..5cad81c249 --- /dev/null +++ b/cpp/src/utilities/determinism_log.hpp @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#ifndef CUOPT_DETERMINISM_LOG +#define CUOPT_DETERMINISM_LOG(logger, ...) \ + do { \ + } while (0) +#endif + +#ifndef CUOPT_DETERMINISM_LOG +#define CUOPT_DETERMINISM_LOG(...) \ + do { \ + } while (0) +#endif + +#ifndef CUOPT_DETERMINISM_LOG +#define CUOPT_DETERMINISM_LOG(...) \ + do { \ + } while (0) +#endif diff --git a/cpp/src/utilities/models/fj_predictor/header.h b/cpp/src/utilities/models/fj_predictor/header.h new file mode 100644 index 0000000000..ccae87627f --- /dev/null +++ b/cpp/src/utilities/models/fj_predictor/header.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +class fj_predictor { + public: + union Entry { + int missing; + double fvalue; + int qvalue; + }; + + static int32_t get_num_target(void); + static void get_num_class(int32_t* out); + static int32_t get_num_feature(void); + static const char* get_threshold_type(void); + static const char* get_leaf_output_type(void); + static void predict(union Entry* data, int pred_margin, double* result); + static void postprocess(double* result); + static int quantize(double val, unsigned fid); + + // Feature names + static constexpr int NUM_FEATURES = 12; + static const char* feature_names[NUM_FEATURES]; +}; // class fj_predictor diff --git a/cpp/src/utilities/models/fj_predictor/main.cpp b/cpp/src/utilities/models/fj_predictor/main.cpp new file mode 100644 index 0000000000..ac5cd1ed11 --- /dev/null +++ b/cpp/src/utilities/models/fj_predictor/main.cpp @@ -0,0 +1,11828 @@ + +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "header.h" + +#if defined(__clang__) || defined(__GNUC__) +#define LIKELY(x) __builtin_expect(!!(x), 1) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define LIKELY(x) (x) +#define UNLIKELY(x) (x) +#endif +#define N_TARGET 1 +#define MAX_N_CLASS 1 + +const unsigned char is_categorical[] = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +}; +static const int32_t num_class[] = { + 1, +}; + +int32_t fj_predictor::get_num_target(void) { return N_TARGET; } +void fj_predictor::get_num_class(int32_t* out) +{ + for (int i = 0; i < N_TARGET; ++i) { + out[i] = num_class[i]; + } +} +int32_t fj_predictor::get_num_feature(void) { return 12; } +const char* fj_predictor::get_threshold_type(void) { return "float64"; } +const char* fj_predictor::get_leaf_output_type(void) { return "float64"; } + +void fj_predictor::predict(union Entry* data, int pred_margin, double* result) +{ + // Quantize data + for (int i = 0; i < 12; ++i) { + if (data[i].missing != -1 && !is_categorical[i]) { + data[i].qvalue = quantize(data[i].fvalue, i); + } + } + + unsigned int tmp; + if (UNLIKELY(false || (data[0].qvalue <= 186))) { + if (LIKELY(false || (data[0].qvalue <= 98))) { + if (UNLIKELY(false || (data[0].qvalue <= 44))) { + result[0] += 22901.255344838846; + } else { + if (LIKELY(false || (data[0].qvalue <= 74))) { + result[0] += 23329.74375127941; + } else { + result[0] += 23615.614222033248; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 144))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + result[0] += 24209.472824119093; + } else { + if (LIKELY(false || (data[6].qvalue <= 100))) { + result[0] += 23869.313116934638; + } else { + result[0] += 23405.54395751655; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 88))) { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 24902.415701614485; + } else { + result[0] += 24520.27915996134; + } + } else { + result[0] += 23891.56119652762; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 262))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (UNLIKELY(false || (data[0].qvalue <= 218))) { + result[0] += 25411.420502457517; + } else { + result[0] += 25917.434788937655; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[1].qvalue <= 116))) { + if (LIKELY(false || (data[0].qvalue <= 224))) { + result[0] += 24824.90282590602; + } else { + result[0] += 25260.302972714355; + } + } else { + result[0] += 24241.872897906287; + } + } else { + result[0] += 23668.087700157346; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 112))) { + if (UNLIKELY(false || (data[0].qvalue <= 312))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 26558.857562746103; + } else { + result[0] += 26202.38213380121; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 102))) { + result[0] += 25849.940581446823; + } else { + result[0] += 25230.335505164217; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 92))) { + if (LIKELY(false || (data[8].qvalue <= 126))) { + result[0] += 26911.44811674291; + } else { + result[0] += 26503.327098291113; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 14))) { + result[0] += 25835.236675109372; + } else { + result[0] += 26394.354620052833; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 430))) { + if (LIKELY(false || (data[6].qvalue <= 152))) { + if (UNLIKELY(false || (data[0].qvalue <= 352))) { + result[0] += 25138.58875113828; + } else { + result[0] += 25959.666864308958; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 406))) { + result[0] += 23920.482800539634; + } else { + result[0] += 24831.920082473396; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 174))) { + if (LIKELY(false || (data[1].qvalue <= 152))) { + result[0] += 26247.05905907499; + } else { + result[0] += 25812.009772798196; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 464))) { + result[0] += 24485.25504118496; + } else { + result[0] += 25971.80731105497; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 188))) { + if (LIKELY(false || (data[0].qvalue <= 104))) { + if (LIKELY(false || (data[0].qvalue <= 50))) { + if (LIKELY(false || (data[0].qvalue <= 24))) { + result[0] += -2102.2335038546057; + } else { + result[0] += -1814.143470277424; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 62))) { + result[0] += -1350.4930205440783; + } else { + result[0] += -1691.59714939238; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 62))) { + if (LIKELY(false || (data[0].qvalue <= 156))) { + if (UNLIKELY(false || (data[0].qvalue <= 126))) { + result[0] += -925.0448121271659; + } else { + result[0] += -609.5991225040165; + } + } else { + result[0] += -202.96527072586937; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 116))) { + if (LIKELY(false || (data[0].qvalue <= 152))) { + result[0] += -1181.817220363881; + } else { + result[0] += -759.0207821022941; + } + } else { + result[0] += -1560.8110503642508; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 266))) { + if (LIKELY(false || (data[7].qvalue <= 66))) { + if (LIKELY(false || (data[0].qvalue <= 228))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += 578.7799431040581; + } else { + result[0] += 127.52408840507688; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 933.7806696735934; + } else { + result[0] += 471.418662728938; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 142))) { + if (LIKELY(false || (data[7].qvalue <= 128))) { + if (LIKELY(false || (data[6].qvalue <= 100))) { + result[0] += 24.61431879503551; + } else { + result[0] += -514.7441916892188; + } + } else { + result[0] += -840.288058328428; + } + } else { + result[0] += -1369.132803656919; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 88))) { + if (UNLIKELY(false || (data[0].qvalue <= 320))) { + if (LIKELY(false || (data[6].qvalue <= 60))) { + result[0] += 1297.8937605229921; + } else { + if (UNLIKELY(false || (data[9].qvalue <= 48))) { + result[0] += 159.84923661334855; + } else { + result[0] += 806.892953963784; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 92))) { + result[0] += 1631.4555218267105; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 14))) { + result[0] += 602.1591975811427; + } else { + result[0] += 1225.4498129353617; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 346))) { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[7].qvalue <= 134))) { + result[0] += 477.26815448941943; + } else { + result[0] += -266.49089797874365; + } + } else { + result[0] += -1085.9174116108277; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 136))) { + if (UNLIKELY(false || (data[9].qvalue <= 30))) { + result[0] += 710.5340339475686; + } else { + result[0] += 1160.9702839561826; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 426))) { + result[0] += -336.2082424932214; + } else { + result[0] += 697.2377446648411; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 184))) { + if (UNLIKELY(false || (data[0].qvalue <= 92))) { + if (UNLIKELY(false || (data[0].qvalue <= 38))) { + result[0] += -1827.1103815856493; + } else { + if (LIKELY(false || (data[6].qvalue <= 92))) { + if (LIKELY(false || (data[0].qvalue <= 68))) { + result[0] += -1460.9279929556785; + } else { + result[0] += -1204.5954946215916; + } + } else { + result[0] += -1703.764420874855; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 136))) { + if (LIKELY(false || (data[7].qvalue <= 62))) { + result[0] += -834.0606253314141; + } else { + result[0] += -1268.097736051363; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 62))) { + if (LIKELY(false || (data[0].qvalue <= 168))) { + result[0] += -450.7445362623278; + } else { + result[0] += -151.5065914814049; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + result[0] += -813.8840731408728; + } else { + result[0] += -1552.9886128870096; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 258))) { + if (LIKELY(false || (data[7].qvalue <= 62))) { + if (UNLIKELY(false || (data[0].qvalue <= 212))) { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 300.18949268400604; + } else { + result[0] += -82.16209082891513; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += 821.3155839598878; + } else { + result[0] += 395.4300146794792; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 142))) { + if (LIKELY(false || (data[7].qvalue <= 134))) { + if (LIKELY(false || (data[6].qvalue <= 94))) { + result[0] += -36.622858072571994; + } else { + result[0] += -472.4429272195769; + } + } else { + result[0] += -886.8842878988258; + } + } else { + result[0] += -1274.9255101678516; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 82))) { + if (UNLIKELY(false || (data[0].qvalue <= 302))) { + if (LIKELY(false || (data[6].qvalue <= 50))) { + result[0] += 1102.9306261545728; + } else { + if (UNLIKELY(false || (data[9].qvalue <= 48))) { + result[0] += 4.269252395168786; + } else { + result[0] += 673.9694942587568; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 48))) { + result[0] += 844.0310059878134; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 336))) { + result[0] += 1254.6320984291067; + } else { + result[0] += 1496.534953542473; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 342))) { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[7].qvalue <= 134))) { + result[0] += 369.8856508010498; + } else { + result[0] += -301.745500216277; + } + } else { + result[0] += -971.5869097857602; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 152))) { + if (LIKELY(false || (data[1].qvalue <= 124))) { + result[0] += 1049.0667116337365; + } else { + result[0] += 655.8288897611133; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 430))) { + result[0] += -483.20221095743983; + } else { + result[0] += 574.5979992744856; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 192))) { + if (LIKELY(false || (data[0].qvalue <= 110))) { + if (LIKELY(false || (data[0].qvalue <= 56))) { + if (UNLIKELY(false || (data[0].qvalue <= 20))) { + result[0] += -1731.6694255800344; + } else { + result[0] += -1463.8734271807368; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 62))) { + result[0] += -1013.7125063069707; + } else { + result[0] += -1295.792308734329; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (LIKELY(false || (data[0].qvalue <= 162))) { + result[0] += -512.040169492541; + } else { + result[0] += -62.54166123570826; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 90))) { + if (LIKELY(false || (data[0].qvalue <= 160))) { + result[0] += -807.711585982142; + } else { + result[0] += -447.3244114975565; + } + } else { + result[0] += -1112.4075523200308; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 274))) { + if (LIKELY(false || (data[7].qvalue <= 68))) { + if (LIKELY(false || (data[0].qvalue <= 236))) { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 434.1102182190951; + } else { + result[0] += 64.6833316545062; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 24))) { + result[0] += 911.5851039227344; + } else { + result[0] += 509.70074610455003; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 36))) { + result[0] += -922.6420870752214; + } else { + if (LIKELY(false || (data[7].qvalue <= 134))) { + result[0] += -43.11646969985167; + } else { + result[0] += -711.5443228439684; + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 112))) { + if (LIKELY(false || (data[7].qvalue <= 50))) { + if (UNLIKELY(false || (data[0].qvalue <= 322))) { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 1217.5857081225881; + } else { + result[0] += 862.7945760545485; + } + } else { + result[0] += 1381.341515141895; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 340))) { + if (LIKELY(false || (data[7].qvalue <= 128))) { + result[0] += 618.2916036455348; + } else { + result[0] += -156.99063121911638; + } + } else { + if (LIKELY(false || (data[10].qvalue <= 78))) { + result[0] += 863.993956946003; + } else { + result[0] += 1217.2914460169281; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 432))) { + if (LIKELY(false || (data[1].qvalue <= 138))) { + if (LIKELY(false || (data[2].qvalue <= 202))) { + result[0] += 554.0458524017479; + } else { + result[0] += -451.2824254081932; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 132))) { + result[0] += -205.44955983215206; + } else { + result[0] += -1114.6634922865608; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 174))) { + if (UNLIKELY(false || (data[8].qvalue <= 44))) { + result[0] += 434.76778433173723; + } else { + result[0] += 874.836013742844; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + result[0] += -415.8493090687832; + } else { + result[0] += 895.0728029685396; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 180))) { + if (UNLIKELY(false || (data[0].qvalue <= 86))) { + if (UNLIKELY(false || (data[0].qvalue <= 32))) { + result[0] += -1510.0101454113396; + } else { + if (LIKELY(false || (data[1].qvalue <= 78))) { + result[0] += -1126.5760081015337; + } else { + result[0] += -1392.4200114552195; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 128))) { + if (LIKELY(false || (data[1].qvalue <= 78))) { + result[0] += -742.4787091372192; + } else { + result[0] += -1128.3169223737384; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 82))) { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += -217.1747383855503; + } else { + result[0] += -505.7066609860621; + } + } else { + result[0] += -926.1157369421153; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 252))) { + if (LIKELY(false || (data[7].qvalue <= 50))) { + if (UNLIKELY(false || (data[0].qvalue <= 208))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += 312.45787313138067; + } else { + result[0] += -10.665507478881368; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 562.9336214602067; + } else { + result[0] += 238.03704733164884; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 116))) { + if (LIKELY(false || (data[7].qvalue <= 126))) { + if (LIKELY(false || (data[0].qvalue <= 212))) { + result[0] += -279.2242547615078; + } else { + result[0] += 42.62552906704628; + } + } else { + result[0] += -677.2319392589053; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 152))) { + result[0] += -619.1669830813266; + } else { + result[0] += -1313.98491170941; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 76))) { + if (UNLIKELY(false || (data[0].qvalue <= 296))) { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 874.524930910277; + } else { + result[0] += 493.74627459103635; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 48))) { + if (UNLIKELY(false || (data[10].qvalue <= 32))) { + result[0] += 305.25905254947634; + } else { + result[0] += 893.8383559035191; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 330))) { + result[0] += 1017.5358679778622; + } else { + result[0] += 1243.0211544735387; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 330))) { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (UNLIKELY(false || (data[9].qvalue <= 40))) { + result[0] += -310.9808580488324; + } else { + result[0] += 277.1404180803976; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 68))) { + result[0] += -1191.3641199442363; + } else { + result[0] += -376.5469216582162; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[7].qvalue <= 134))) { + result[0] += 906.2480551197423; + } else { + result[0] += 554.4928258443013; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 424))) { + result[0] += -334.1159618884123; + } else { + result[0] += 515.865642037843; + } + } + } + } + } + } + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (UNLIKELY(false || (data[3].qvalue <= 44))) { + if (LIKELY(false || (data[7].qvalue <= 24))) { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + if (LIKELY(false || (data[2].qvalue <= 128))) { + result[0] += 200.2716866984727; + } else { + result[0] += -33.31807134567322; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 34))) { + if (LIKELY(false || (data[2].qvalue <= 124))) { + result[0] += 2.1267132045491657; + } else { + result[0] += -376.52573890556465; + } + } else { + result[0] += 146.5594659396158; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 84))) { + if (UNLIKELY(false || (data[6].qvalue <= 40))) { + if (LIKELY(false || (data[2].qvalue <= 88))) { + result[0] += -78.39211587508055; + } else { + result[0] += -211.1780949469055; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 94))) { + result[0] += -50.54335036012877; + } else { + result[0] += 80.00020842727285; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 40))) { + result[0] += -483.63985186717866; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 56))) { + result[0] += 31.9766310228433; + } else { + result[0] += -151.25515710703033; + } + } + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 152))) { + if (LIKELY(false || (data[10].qvalue <= 108))) { + if (UNLIKELY(false || (data[8].qvalue <= 20))) { + if (UNLIKELY(false || (data[8].qvalue <= 0))) { + result[0] += 111.60897433537673; + } else { + result[0] += -38.459174422337334; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 152))) { + result[0] += 39.893177773342615; + } else { + result[0] += 117.1746253290821; + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 40))) { + if (UNLIKELY(false || (data[3].qvalue <= 58))) { + result[0] += -269.8313885970149; + } else { + result[0] += -44.81099090001982; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 50))) { + result[0] += 44.36810505858426; + } else { + result[0] += -17.031285003457658; + } + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 66))) { + result[0] += 29.134840065248483; + } else { + result[0] += -142.57181028298447; + } + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 100))) { + if (LIKELY(false || (data[7].qvalue <= 196))) { + if (UNLIKELY(false || (data[4].qvalue <= 52))) { + result[0] += -122.23715116402353; + } else { + if (UNLIKELY(false || (data[8].qvalue <= 124))) { + result[0] += 97.82653352215696; + } else { + if (UNLIKELY(false || (data[1].qvalue <= 88))) { + result[0] += 19.540553416535854; + } else { + result[0] += -98.26047713895753; + } + } + } + } else { + result[0] += -208.9897177114526; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += -9.013216655506389; + } else { + if (UNLIKELY(false || (data[1].qvalue <= 154))) { + result[0] += -171.41131706373193; + } else { + if (LIKELY(false || (data[7].qvalue <= 188))) { + result[0] += -228.5813161018793; + } else { + result[0] += -325.7003509988099; + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 196))) { + if (LIKELY(false || (data[0].qvalue <= 114))) { + if (LIKELY(false || (data[0].qvalue <= 60))) { + if (UNLIKELY(false || (data[0].qvalue <= 16))) { + result[0] += -1431.9188559535999; + } else { + result[0] += -1190.2844870798897; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 82))) { + result[0] += -829.9317534554965; + } else { + result[0] += -1180.4578431880107; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 94))) { + if (LIKELY(false || (data[0].qvalue <= 166))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += -258.2731590553095; + } else { + result[0] += -523.7930385002326; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 24))) { + result[0] += 148.2458712176789; + } else { + result[0] += -208.97850691995546; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 152))) { + result[0] += -766.4846303120403; + } else { + result[0] += -1387.2597830966474; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 282))) { + if (LIKELY(false || (data[7].qvalue <= 82))) { + if (LIKELY(false || (data[6].qvalue <= 54))) { + if (UNLIKELY(false || (data[0].qvalue <= 234))) { + result[0] += 347.56940876633854; + } else { + result[0] += 659.3973774082955; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 48))) { + result[0] += -299.8000443073724; + } else { + result[0] += 213.67905068511027; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + result[0] += -241.4835022721958; + } else { + result[0] += -964.8334869254049; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 96))) { + if (LIKELY(false || (data[7].qvalue <= 50))) { + if (UNLIKELY(false || (data[0].qvalue <= 326))) { + if (LIKELY(false || (data[6].qvalue <= 54))) { + result[0] += 1024.8241421866958; + } else { + result[0] += 691.4425294017764; + } + } else { + result[0] += 1134.476489243314; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 348))) { + if (LIKELY(false || (data[7].qvalue <= 178))) { + result[0] += 607.890368257065; + } else { + result[0] += -428.4659382893519; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 414))) { + result[0] += 1014.748894801393; + } else { + result[0] += 597.6335980398155; + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 156))) { + if (UNLIKELY(false || (data[0].qvalue <= 366))) { + if (LIKELY(false || (data[7].qvalue <= 102))) { + result[0] += 342.26282113720123; + } else { + result[0] += -230.30740062057927; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 104))) { + result[0] += 813.8543527953774; + } else { + result[0] += 415.6624160363879; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 446))) { + if (LIKELY(false || (data[6].qvalue <= 174))) { + result[0] += -259.7429923675005; + } else { + result[0] += -1426.7951683026927; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 174))) { + result[0] += 762.73962100054; + } else { + result[0] += 231.27954235732147; + } + } + } + } + } + } + if (LIKELY(false || (data[1].qvalue <= 152))) { + if (UNLIKELY(false || (data[3].qvalue <= 44))) { + if (LIKELY(false || (data[7].qvalue <= 24))) { + if (UNLIKELY(false || (data[7].qvalue <= 2))) { + if (UNLIKELY(false || (data[2].qvalue <= 10))) { + result[0] += 301.84658146708784; + } else { + result[0] += 144.51444115717683; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 34))) { + if (UNLIKELY(false || (data[3].qvalue <= 0))) { + result[0] += -133.3520185035297; + } else { + result[0] += 7.825198511802189; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 20))) { + result[0] += 186.55141223016244; + } else { + result[0] += 66.07816401815487; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 84))) { + if (UNLIKELY(false || (data[9].qvalue <= 30))) { + if (LIKELY(false || (data[1].qvalue <= 30))) { + result[0] += 190.81711934969553; + } else { + result[0] += -49.06287353594854; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 6))) { + result[0] += -190.1399731591398; + } else { + result[0] += -58.312564377257914; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 40))) { + result[0] += -486.8565320491534; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 122))) { + result[0] += -23.79376937838657; + } else { + result[0] += -193.64446213252586; + } + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (LIKELY(false || (data[10].qvalue <= 108))) { + if (UNLIKELY(false || (data[2].qvalue <= 8))) { + if (UNLIKELY(false || (data[10].qvalue <= 0))) { + result[0] += 154.980784796019; + } else { + result[0] += -70.70779749773081; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 166))) { + result[0] += 42.70546399312704; + } else { + result[0] += 231.67165411885554; + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 40))) { + if (UNLIKELY(false || (data[3].qvalue <= 58))) { + result[0] += -276.0507296705326; + } else { + result[0] += -48.24184527320793; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 50))) { + result[0] += 46.29969181016952; + } else { + result[0] += -22.46817209136037; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 196))) { + if (LIKELY(false || (data[2].qvalue <= 184))) { + if (UNLIKELY(false || (data[9].qvalue <= 70))) { + result[0] += 259.7266642867935; + } else { + result[0] += -33.9062832423356; + } + } else { + result[0] += -130.08036962157004; + } + } else { + result[0] += -206.5135454754138; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 188))) { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += 21.01496012390576; + } else { + if (LIKELY(false || (data[9].qvalue <= 16))) { + if (LIKELY(false || (data[9].qvalue <= 2))) { + result[0] += -166.20109103415467; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 22))) { + result[0] += 43.76493225425393; + } else { + result[0] += -110.8984323288921; + } + } + } else { + result[0] += -265.77242681150005; + } + } + } else { + result[0] += -316.46151789497696; + } + } + if (LIKELY(false || (data[1].qvalue <= 152))) { + if (UNLIKELY(false || (data[3].qvalue <= 44))) { + if (LIKELY(false || (data[7].qvalue <= 42))) { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + if (LIKELY(false || (data[2].qvalue <= 128))) { + result[0] += 196.29561739518834; + } else { + result[0] += -31.524131824672523; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 154))) { + if (UNLIKELY(false || (data[4].qvalue <= 6))) { + result[0] += 101.91406015487678; + } else { + result[0] += 1.3921745492571436; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 20))) { + result[0] += -150.8055895533361; + } else { + result[0] += 146.0669399096082; + } + } + } + } else { + if (LIKELY(false || (data[3].qvalue <= 38))) { + if (LIKELY(false || (data[7].qvalue <= 122))) { + if (LIKELY(false || (data[4].qvalue <= 76))) { + result[0] += -101.79580164134511; + } else { + result[0] += 51.3302149880617; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 108))) { + result[0] += -334.80985020089327; + } else { + result[0] += -145.88767910265673; + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 82))) { + result[0] += -560.2505700484534; + } else { + result[0] += -213.96025392317247; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (LIKELY(false || (data[10].qvalue <= 96))) { + if (UNLIKELY(false || (data[8].qvalue <= 20))) { + if (UNLIKELY(false || (data[8].qvalue <= 0))) { + result[0] += 105.68674793264172; + } else { + result[0] += -43.57721621182288; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + result[0] += -147.9863609603396; + } else { + result[0] += 50.11914192647265; + } + } + } else { + if (LIKELY(false || (data[8].qvalue <= 138))) { + if (LIKELY(false || (data[3].qvalue <= 120))) { + result[0] += -21.50623989909495; + } else { + result[0] += 36.087102588326; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 172))) { + result[0] += -401.56511373374224; + } else { + result[0] += -47.54506742735205; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 196))) { + if (LIKELY(false || (data[10].qvalue <= 136))) { + if (LIKELY(false || (data[4].qvalue <= 70))) { + result[0] += -58.788243146060125; + } else { + result[0] += 35.01063889842818; + } + } else { + result[0] += -167.15525202440935; + } + } else { + result[0] += -185.86643283746199; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 188))) { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += 18.914144121003567; + } else { + if (LIKELY(false || (data[9].qvalue <= 16))) { + if (LIKELY(false || (data[9].qvalue <= 2))) { + result[0] += -149.58318971768668; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 22))) { + result[0] += 39.39279508530201; + } else { + result[0] += -99.81036974381612; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 28))) { + result[0] += -574.5809531834742; + } else { + result[0] += -223.51903615680277; + } + } + } + } else { + result[0] += -284.8251127560464; + } + } + if (UNLIKELY(false || (data[0].qvalue <= 178))) { + if (UNLIKELY(false || (data[0].qvalue <= 84))) { + if (UNLIKELY(false || (data[0].qvalue <= 34))) { + result[0] += -1224.3672336316197; + } else { + if (LIKELY(false || (data[6].qvalue <= 62))) { + result[0] += -890.0738935144229; + } else { + result[0] += -1083.6868213970283; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 88))) { + if (LIKELY(false || (data[0].qvalue <= 132))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += -449.253175915189; + } else { + result[0] += -666.9058347442864; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 36))) { + result[0] += -171.0454878027209; + } else { + result[0] += -404.9936499215746; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 108))) { + result[0] += -738.8736900979811; + } else { + result[0] += -1156.8338657457468; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 246))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += 376.54293691682807; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 204))) { + result[0] += -19.164395129970544; + } else { + result[0] += 229.71203236622483; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 116))) { + if (LIKELY(false || (data[7].qvalue <= 92))) { + result[0] += -93.71602815702187; + } else { + result[0] += -442.3743373003131; + } + } else { + result[0] += -794.9240557679232; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 96))) { + if (UNLIKELY(false || (data[0].qvalue <= 294))) { + if (LIKELY(false || (data[7].qvalue <= 44))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += 776.7722682804047; + } else { + result[0] += 551.7601378282178; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 126))) { + result[0] += 359.00317634305014; + } else { + result[0] += -261.0799150747045; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 54))) { + if (UNLIKELY(false || (data[0].qvalue <= 340))) { + result[0] += 868.4127360515845; + } else { + result[0] += 1030.2866556923334; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 174))) { + result[0] += 759.8335007432447; + } else { + result[0] += 375.57847778528725; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 362))) { + if (LIKELY(false || (data[1].qvalue <= 124))) { + if (LIKELY(false || (data[7].qvalue <= 152))) { + result[0] += 192.76419647400402; + } else { + result[0] += -627.210504284817; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 122))) { + result[0] += -248.81615213762672; + } else { + result[0] += -969.1369200758126; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 152))) { + if (LIKELY(false || (data[1].qvalue <= 124))) { + result[0] += 773.3204928636587; + } else { + result[0] += 406.4377394893374; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 448))) { + result[0] += -133.64446813672; + } else { + result[0] += 531.8523379173158; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 198))) { + if (LIKELY(false || (data[0].qvalue <= 116))) { + if (LIKELY(false || (data[0].qvalue <= 64))) { + if (UNLIKELY(false || (data[0].qvalue <= 14))) { + result[0] += -1181.6504962663746; + } else { + result[0] += -963.5370227469084; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 70))) { + result[0] += -638.280415142769; + } else { + result[0] += -906.2068637569266; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 78))) { + if (UNLIKELY(false || (data[0].qvalue <= 160))) { + result[0] += -355.3777345047579; + } else { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 38.63569197354808; + } else { + result[0] += -203.4298594886395; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 106))) { + result[0] += -501.25518228442854; + } else { + result[0] += -904.0956495312481; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 284))) { + if (LIKELY(false || (data[6].qvalue <= 74))) { + if (LIKELY(false || (data[6].qvalue <= 46))) { + if (LIKELY(false || (data[0].qvalue <= 242))) { + result[0] += 325.0351233983085; + } else { + result[0] += 574.7980032330352; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 240))) { + result[0] += 50.603122386774714; + } else { + result[0] += 307.4265167122256; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 124))) { + if (LIKELY(false || (data[2].qvalue <= 200))) { + result[0] += -75.63749275285782; + } else { + result[0] += -878.8670678802445; + } + } else { + result[0] += -784.825049044781; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 108))) { + if (LIKELY(false || (data[1].qvalue <= 82))) { + if (LIKELY(false || (data[9].qvalue <= 128))) { + if (LIKELY(false || (data[0].qvalue <= 354))) { + result[0] += 768.9030375927771; + } else { + result[0] += 982.7632961026947; + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 26))) { + result[0] += 881.7519427243232; + } else { + result[0] += 460.8225104674566; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 352))) { + result[0] += 336.6542893696312; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 12))) { + result[0] += 232.48634402594894; + } else { + result[0] += 754.2862591795262; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 434))) { + if (LIKELY(false || (data[1].qvalue <= 138))) { + if (LIKELY(false || (data[10].qvalue <= 116))) { + result[0] += 486.1115775722369; + } else { + result[0] += -240.0215539033738; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 152))) { + result[0] += -340.1982716887718; + } else { + result[0] += -1142.2235138961084; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 174))) { + if (UNLIKELY(false || (data[2].qvalue <= 106))) { + result[0] += 356.11751909622217; + } else { + result[0] += 708.5483451506925; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + result[0] += -425.3011321722016; + } else { + result[0] += 737.8486431169086; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 176))) { + if (UNLIKELY(false || (data[0].qvalue <= 82))) { + if (UNLIKELY(false || (data[0].qvalue <= 30))) { + result[0] += -1008.2347845448372; + } else { + if (LIKELY(false || (data[6].qvalue <= 92))) { + result[0] += -759.2605409681978; + } else { + result[0] += -1002.0185264155406; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 90))) { + if (UNLIKELY(false || (data[0].qvalue <= 122))) { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += -430.8085418741898; + } else { + result[0] += -615.9206678101054; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += -160.1037858692929; + } else { + result[0] += -361.25591627953935; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + result[0] += -677.2268849569755; + } else { + result[0] += -1084.1504346623935; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 256))) { + if (LIKELY(false || (data[7].qvalue <= 48))) { + if (LIKELY(false || (data[0].qvalue <= 216))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += 219.18790221063765; + } else { + result[0] += 8.224416225928314; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += 473.5370228126004; + } else { + result[0] += 241.42467866161678; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 126))) { + if (LIKELY(false || (data[6].qvalue <= 100))) { + result[0] += -37.498571958273004; + } else { + result[0] += -365.5446484967875; + } + } else { + result[0] += -627.0927668810837; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 88))) { + if (UNLIKELY(false || (data[0].qvalue <= 306))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += 703.8669257065242; + } else { + result[0] += 489.86267404805426; + } + } else { + result[0] += 234.87338838535948; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 64))) { + if (LIKELY(false || (data[0].qvalue <= 356))) { + result[0] += 717.6793071720917; + } else { + result[0] += 869.7251021367146; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 6))) { + result[0] += 129.6795962020335; + } else { + result[0] += 601.4460117834096; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 374))) { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[7].qvalue <= 176))) { + result[0] += 214.20152575675183; + } else { + result[0] += -537.860116518967; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 24))) { + result[0] += 39.286767186140054; + } else { + result[0] += -860.9173103201238; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 156))) { + if (UNLIKELY(false || (data[6].qvalue <= 36))) { + result[0] += -207.54940006849526; + } else { + result[0] += 555.8581322313454; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 448))) { + result[0] += -233.03910512550647; + } else { + result[0] += 411.0057503246483; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 200))) { + if (UNLIKELY(false || (data[0].qvalue <= 102))) { + if (UNLIKELY(false || (data[0].qvalue <= 42))) { + result[0] += -879.2505320646146; + } else { + if (LIKELY(false || (data[6].qvalue <= 100))) { + result[0] += -613.8765129117388; + } else { + result[0] += -898.2402256092773; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (UNLIKELY(false || (data[0].qvalue <= 152))) { + result[0] += -310.0254085675876; + } else { + result[0] += -23.926126961634566; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (UNLIKELY(false || (data[0].qvalue <= 150))) { + result[0] += -532.7012177870323; + } else { + result[0] += -303.0535459955343; + } + } else { + result[0] += -885.9999951852365; + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 96))) { + if (UNLIKELY(false || (data[0].qvalue <= 288))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (UNLIKELY(false || (data[0].qvalue <= 236))) { + result[0] += 215.01429644416353; + } else { + result[0] += 421.53246886972823; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 48))) { + result[0] += -286.0785455584352; + } else { + result[0] += 116.1256792417381; + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 120))) { + if (LIKELY(false || (data[8].qvalue <= 118))) { + if (LIKELY(false || (data[0].qvalue <= 414))) { + result[0] += 706.4179843328923; + } else { + result[0] += 374.35058115512874; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 28))) { + result[0] += -23.434409262940505; + } else { + result[0] += 567.1115038047287; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 386))) { + if (LIKELY(false || (data[8].qvalue <= 18))) { + result[0] += 273.6642642670474; + } else { + result[0] += -1046.2285662989975; + } + } else { + result[0] += 487.25359122330957; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 354))) { + if (LIKELY(false || (data[6].qvalue <= 152))) { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (LIKELY(false || (data[4].qvalue <= 98))) { + result[0] += 86.77607178167547; + } else { + result[0] += -327.9227215464309; + } + } else { + result[0] += -702.536120284601; + } + } else { + result[0] += -816.76285821889; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 168))) { + if (LIKELY(false || (data[4].qvalue <= 104))) { + if (LIKELY(false || (data[2].qvalue <= 202))) { + result[0] += 585.7714665368296; + } else { + result[0] += 211.97898615341128; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 446))) { + result[0] += 45.872711470194794; + } else { + result[0] += 596.7727695600357; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 460))) { + if (LIKELY(false || (data[6].qvalue <= 174))) { + result[0] += -110.3734753492252; + } else { + result[0] += -993.643156799906; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 134))) { + result[0] += 513.9132377712247; + } else { + result[0] += -156.94258247387484; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 174))) { + if (UNLIKELY(false || (data[0].qvalue <= 78))) { + if (UNLIKELY(false || (data[0].qvalue <= 28))) { + result[0] += -825.7182786124127; + } else { + if (LIKELY(false || (data[1].qvalue <= 78))) { + result[0] += -620.5383438315085; + } else { + result[0] += -812.4844205870289; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 78))) { + if (UNLIKELY(false || (data[0].qvalue <= 124))) { + result[0] += -409.88504033635036; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += -72.60757573397173; + } else { + result[0] += -250.66315233138621; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 32))) { + result[0] += -830.1444150421403; + } else { + result[0] += -512.6141494941938; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 244))) { + if (LIKELY(false || (data[7].qvalue <= 48))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + if (UNLIKELY(false || (data[10].qvalue <= 8))) { + result[0] += 654.5140923867807; + } else { + result[0] += 216.223797321274; + } + } else { + result[0] += 59.127335346599445; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 42))) { + if (LIKELY(false || (data[7].qvalue <= 120))) { + result[0] += -408.9401043985653; + } else { + result[0] += -900.5928970741843; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 134))) { + result[0] += -88.4273489833162; + } else { + result[0] += -494.5124549516256; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 126))) { + if (UNLIKELY(false || (data[0].qvalue <= 316))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += 577.7159751120596; + } else { + result[0] += 390.25605025593114; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 100))) { + result[0] += 225.37808240171967; + } else { + result[0] += -134.1896384710644; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 40))) { + if (UNLIKELY(false || (data[0].qvalue <= 380))) { + result[0] += 26.38536976794332; + } else { + result[0] += 404.9857810684197; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 206))) { + result[0] += 640.5372498970368; + } else { + result[0] += 146.7850504947837; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 398))) { + if (UNLIKELY(false || (data[9].qvalue <= 46))) { + if (UNLIKELY(false || (data[2].qvalue <= 32))) { + result[0] += -69.72606543209433; + } else { + result[0] += -890.4547484128167; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 194))) { + result[0] += 43.29913016336363; + } else { + result[0] += -1135.7449808644187; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 156))) { + if (UNLIKELY(false || (data[4].qvalue <= 72))) { + result[0] += 619.8183215898755; + } else { + result[0] += 285.5489715529262; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 448))) { + result[0] += -358.75757336334124; + } else { + result[0] += 304.16119148341835; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 202))) { + if (LIKELY(false || (data[0].qvalue <= 118))) { + if (LIKELY(false || (data[0].qvalue <= 66))) { + if (UNLIKELY(false || (data[0].qvalue <= 10))) { + result[0] += -817.0480700539837; + } else { + result[0] += -635.8069750267712; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 46))) { + result[0] += -345.96669659122944; + } else { + result[0] += -534.0482572373161; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 90))) { + if (LIKELY(false || (data[0].qvalue <= 170))) { + result[0] += -211.68858516839794; + } else { + if (LIKELY(false || (data[6].qvalue <= 50))) { + result[0] += 62.76650514311201; + } else { + result[0] += -136.44892442998687; + } + } + } else { + result[0] += -491.2641598575405; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 134))) { + if (UNLIKELY(false || (data[0].qvalue <= 292))) { + if (LIKELY(false || (data[7].qvalue <= 48))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + if (LIKELY(false || (data[6].qvalue <= 32))) { + result[0] += 331.57071848199166; + } else { + result[0] += 679.3219985689888; + } + } else { + result[0] += 218.00776335345145; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 42))) { + result[0] += -277.030356546739; + } else { + result[0] += 67.28341346905603; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 40))) { + if (LIKELY(false || (data[0].qvalue <= 428))) { + if (LIKELY(false || (data[6].qvalue <= 134))) { + result[0] += 324.7275088735298; + } else { + result[0] += -419.6036244916827; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 90))) { + result[0] += 170.7438608997793; + } else { + result[0] += 713.0065984964028; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (LIKELY(false || (data[0].qvalue <= 356))) { + result[0] += 471.87466419027; + } else { + result[0] += 637.1788630246149; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 408))) { + result[0] += -529.7467726888689; + } else { + result[0] += 412.8057422873779; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 404))) { + if (UNLIKELY(false || (data[9].qvalue <= 46))) { + result[0] += -712.0507049531527; + } else { + if (LIKELY(false || (data[7].qvalue <= 194))) { + if (LIKELY(false || (data[2].qvalue <= 202))) { + result[0] += 36.347398334304295; + } else { + result[0] += -675.8496819191378; + } + } else { + result[0] += -1026.391767021813; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + if (LIKELY(false || (data[6].qvalue <= 174))) { + if (LIKELY(false || (data[2].qvalue <= 218))) { + result[0] += 300.59738999141763; + } else { + result[0] += -768.8823600737215; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 462))) { + result[0] += -815.3971702532364; + } else { + result[0] += -98.98096938280344; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 188))) { + result[0] += 772.6721143780254; + } else { + result[0] += 184.03196143296145; + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 204))) { + if (UNLIKELY(false || (data[0].qvalue <= 106))) { + if (LIKELY(false || (data[0].qvalue <= 52))) { + result[0] += -628.1023483072281; + } else { + if (LIKELY(false || (data[7].qvalue <= 90))) { + result[0] += -417.2006348985446; + } else { + result[0] += -652.72384554584; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 78))) { + if (UNLIKELY(false || (data[0].qvalue <= 150))) { + result[0] += -244.5303558275764; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += 81.86220471198173; + } else { + result[0] += -93.51539141150997; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 42))) { + result[0] += -604.2798075362174; + } else { + result[0] += -312.7206975646505; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 76))) { + if (UNLIKELY(false || (data[0].qvalue <= 280))) { + if (UNLIKELY(false || (data[7].qvalue <= 30))) { + if (UNLIKELY(false || (data[7].qvalue <= 2))) { + result[0] += 807.0167727723248; + } else { + result[0] += 289.0442076002897; + } + } else { + result[0] += 124.5150431079561; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 78))) { + if (UNLIKELY(false || (data[0].qvalue <= 330))) { + if (UNLIKELY(false || (data[7].qvalue <= 30))) { + result[0] += 565.5660231034293; + } else { + result[0] += 342.6451209197193; + } + } else { + result[0] += 592.3830913005597; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 6))) { + if (LIKELY(false || (data[0].qvalue <= 444))) { + result[0] += -430.7701175762352; + } else { + result[0] += 555.2205548100666; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 356))) { + result[0] += 220.55563498286244; + } else { + result[0] += 609.8515804605789; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 364))) { + if (UNLIKELY(false || (data[9].qvalue <= 40))) { + if (LIKELY(false || (data[7].qvalue <= 112))) { + result[0] += -186.288076755465; + } else { + result[0] += -654.6528922882809; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (LIKELY(false || (data[7].qvalue <= 180))) { + result[0] += 134.73234216388812; + } else { + result[0] += -411.6205521150166; + } + } else { + result[0] += -601.6441239663638; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 152))) { + if (LIKELY(false || (data[2].qvalue <= 214))) { + if (UNLIKELY(false || (data[8].qvalue <= 48))) { + result[0] += 159.54229602414563; + } else { + result[0] += 399.7957684439738; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -623.3887244049713; + } else { + result[0] += 412.91921366956416; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 448))) { + if (LIKELY(false || (data[9].qvalue <= 18))) { + result[0] += -725.573342147161; + } else { + result[0] += 232.5245626809719; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 62))) { + result[0] += 781.8973840507092; + } else { + result[0] += 102.27963999730173; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 206))) { + if (LIKELY(false || (data[0].qvalue <= 118))) { + if (LIKELY(false || (data[0].qvalue <= 70))) { + result[0] += -541.6072754781043; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 40))) { + result[0] += -266.3645156358689; + } else { + result[0] += -438.26985938213795; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 90))) { + if (LIKELY(false || (data[0].qvalue <= 170))) { + result[0] += -171.34773263938322; + } else { + result[0] += -11.303977068049283; + } + } else { + result[0] += -407.71415355827446; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 126))) { + if (UNLIKELY(false || (data[0].qvalue <= 298))) { + if (LIKELY(false || (data[1].qvalue <= 76))) { + if (UNLIKELY(false || (data[7].qvalue <= 16))) { + if (LIKELY(false || (data[5].qvalue <= 46))) { + result[0] += 305.49013378917147; + } else { + result[0] += 766.8660544561868; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 12))) { + result[0] += -212.69432422518977; + } else { + result[0] += 203.45215339752818; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 100))) { + if (UNLIKELY(false || (data[2].qvalue <= 6))) { + result[0] += -1306.979742409561; + } else { + result[0] += 84.56250938982618; + } + } else { + result[0] += -296.8758971391223; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 92))) { + if (LIKELY(false || (data[2].qvalue <= 212))) { + if (UNLIKELY(false || (data[7].qvalue <= 16))) { + result[0] += 634.2960749274997; + } else { + result[0] += 446.27587321034673; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 442))) { + result[0] += -656.3271831376737; + } else { + result[0] += 649.7533136866848; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 346))) { + if (LIKELY(false || (data[6].qvalue <= 100))) { + result[0] += 272.18984613419735; + } else { + result[0] += -183.6208158354411; + } + } else { + result[0] += 335.6468220356205; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 418))) { + if (LIKELY(false || (data[1].qvalue <= 136))) { + if (LIKELY(false || (data[2].qvalue <= 190))) { + if (LIKELY(false || (data[7].qvalue <= 194))) { + result[0] += 155.81121294253342; + } else { + result[0] += -775.8409014979492; + } + } else { + result[0] += -546.6139529208316; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += 279.2996966849545; + } else { + result[0] += -746.3491117424761; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 174))) { + if (UNLIKELY(false || (data[0].qvalue <= 444))) { + if (UNLIKELY(false || (data[2].qvalue <= 76))) { + result[0] += -794.9146466173518; + } else { + result[0] += 238.59142556484136; + } + } else { + result[0] += 427.50310495259396; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + if (LIKELY(false || (data[4].qvalue <= 112))) { + result[0] += -91.71744859578774; + } else { + result[0] += -899.7782100028055; + } + } else { + result[0] += 481.37569784189236; + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 182))) { + if (UNLIKELY(false || (data[0].qvalue <= 90))) { + if (UNLIKELY(false || (data[0].qvalue <= 26))) { + result[0] += -559.2465183228969; + } else { + if (UNLIKELY(false || (data[6].qvalue <= 46))) { + result[0] += -339.48268673463207; + } else { + result[0] += -477.7038556835741; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 100))) { + if (LIKELY(false || (data[0].qvalue <= 142))) { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += -173.83777524062293; + } else { + result[0] += -308.64588699940225; + } + } else { + result[0] += -87.70355551216173; + } + } else { + result[0] += -460.0791599316315; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 264))) { + if (LIKELY(false || (data[1].qvalue <= 76))) { + if (LIKELY(false || (data[6].qvalue <= 46))) { + if (UNLIKELY(false || (data[3].qvalue <= 34))) { + if (UNLIKELY(false || (data[4].qvalue <= 2))) { + result[0] += 519.5076559696931; + } else { + result[0] += 36.579262885789255; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 8))) { + result[0] += 850.2335034126149; + } else { + result[0] += 225.76397079413547; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 200))) { + result[0] += 31.634082363361337; + } else { + result[0] += -491.7231693795383; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 134))) { + result[0] += -143.97621323045203; + } else { + result[0] += -587.7222010882514; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[1].qvalue <= 76))) { + if (LIKELY(false || (data[9].qvalue <= 128))) { + if (LIKELY(false || (data[0].qvalue <= 328))) { + result[0] += 341.70375333102265; + } else { + result[0] += 492.9064526262677; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 32))) { + result[0] += 291.11846627413223; + } else { + result[0] += -67.70442593342548; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 336))) { + if (UNLIKELY(false || (data[9].qvalue <= 42))) { + result[0] += -178.5440203148846; + } else { + result[0] += 126.46282552428471; + } + } else { + if (LIKELY(false || (data[10].qvalue <= 76))) { + result[0] += 222.41058195803035; + } else { + result[0] += 434.73453399120257; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 426))) { + if (LIKELY(false || (data[9].qvalue <= 68))) { + if (LIKELY(false || (data[0].qvalue <= 416))) { + result[0] += -640.7399976249752; + } else { + result[0] += -167.72938945515875; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 46))) { + result[0] += -1344.5726371900926; + } else { + result[0] += 230.06133289043484; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 168))) { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + result[0] += -84.2627136369062; + } else { + result[0] += 415.7428703783992; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 460))) { + result[0] += -384.29322175155045; + } else { + result[0] += 296.1675101952608; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 214))) { + if (LIKELY(false || (data[0].qvalue <= 122))) { + if (UNLIKELY(false || (data[0].qvalue <= 46))) { + result[0] += -470.73918641723026; + } else { + if (LIKELY(false || (data[7].qvalue <= 90))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += -198.32164705298905; + } else { + result[0] += -321.39079912240294; + } + } else { + result[0] += -498.8080870646863; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 44))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += 317.2121545851497; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 172))) { + result[0] += -88.36582704181914; + } else { + result[0] += 46.798653356184104; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 126))) { + if (LIKELY(false || (data[1].qvalue <= 92))) { + result[0] += -119.53396623757338; + } else { + result[0] += -262.3878864323929; + } + } else { + result[0] += -497.48244794654096; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 134))) { + if (UNLIKELY(false || (data[0].qvalue <= 304))) { + if (LIKELY(false || (data[7].qvalue <= 44))) { + if (UNLIKELY(false || (data[7].qvalue <= 2))) { + result[0] += 746.0045878549049; + } else { + if (LIKELY(false || (data[10].qvalue <= 124))) { + result[0] += 255.0791650107591; + } else { + result[0] += -320.7492571200465; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 90))) { + result[0] += 96.84399754507324; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 18))) { + result[0] += -880.8032147114733; + } else { + result[0] += -73.20637853492896; + } + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 38))) { + result[0] += 461.5993365359995; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 358))) { + if (LIKELY(false || (data[2].qvalue <= 180))) { + result[0] += 212.73235839758036; + } else { + result[0] += -154.54318766724592; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 124))) { + result[0] += 395.0997024493214; + } else { + result[0] += 210.88997718544783; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 404))) { + if (LIKELY(false || (data[7].qvalue <= 192))) { + if (LIKELY(false || (data[10].qvalue <= 112))) { + if (UNLIKELY(false || (data[5].qvalue <= 50))) { + result[0] += -969.6328168630031; + } else { + result[0] += -86.652192967571; + } + } else { + result[0] += -593.8688924415013; + } + } else { + result[0] += -859.0755121136438; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (LIKELY(false || (data[5].qvalue <= 120))) { + if (LIKELY(false || (data[2].qvalue <= 222))) { + result[0] += 179.3501661826762; + } else { + result[0] += -755.4195928241176; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 464))) { + result[0] += -1101.7895944684535; + } else { + result[0] += -169.1089745696061; + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 132))) { + result[0] += 735.5941243655279; + } else { + result[0] += -9.689280563857821; + } + } + } + } + } + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (UNLIKELY(false || (data[3].qvalue <= 34))) { + if (LIKELY(false || (data[7].qvalue <= 16))) { + if (UNLIKELY(false || (data[7].qvalue <= 2))) { + result[0] += 241.07465429893853; + } else { + if (UNLIKELY(false || (data[5].qvalue <= 2))) { + result[0] += -167.05234881826266; + } else { + if (LIKELY(false || (data[9].qvalue <= 160))) { + result[0] += 23.635093021555477; + } else { + result[0] += 247.95569232139462; + } + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 24))) { + if (UNLIKELY(false || (data[5].qvalue <= 26))) { + result[0] += -11.35953345193686; + } else { + if (LIKELY(false || (data[8].qvalue <= 46))) { + result[0] += -241.87320597288937; + } else { + result[0] += -558.2222728620737; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 22))) { + result[0] += -717.6843774931726; + } else { + if (UNLIKELY(false || (data[5].qvalue <= 18))) { + result[0] += -119.9905475497901; + } else { + result[0] += 12.781811278483842; + } + } + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 54))) { + if (UNLIKELY(false || (data[5].qvalue <= 78))) { + if (UNLIKELY(false || (data[8].qvalue <= 68))) { + if (UNLIKELY(false || (data[8].qvalue <= 22))) { + result[0] += -383.60019278447663; + } else { + result[0] += 70.39617109267411; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 62))) { + result[0] += 14.894390887322576; + } else { + result[0] += -246.88224072530934; + } + } + } else { + if (LIKELY(false || (data[10].qvalue <= 134))) { + if (LIKELY(false || (data[9].qvalue <= 32))) { + result[0] += -26.468777991984183; + } else { + result[0] += 124.62962202584231; + } + } else { + result[0] += -125.32103441709326; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 98))) { + if (UNLIKELY(false || (data[6].qvalue <= 50))) { + if (LIKELY(false || (data[8].qvalue <= 120))) { + result[0] += 79.94063628810217; + } else { + result[0] += -99.91998726218483; + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 54))) { + result[0] += -102.42726831173952; + } else { + result[0] += 19.419260663393466; + } + } + } else { + if (LIKELY(false || (data[8].qvalue <= 120))) { + if (LIKELY(false || (data[10].qvalue <= 90))) { + result[0] += 179.7748013112496; + } else { + result[0] += -7.882776932047753; + } + } else { + result[0] += 448.60994004642566; + } + } + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 134))) { + if (LIKELY(false || (data[7].qvalue <= 198))) { + if (LIKELY(false || (data[3].qvalue <= 168))) { + if (LIKELY(false || (data[6].qvalue <= 178))) { + result[0] += -30.950902106565053; + } else { + result[0] += 476.7456995983057; + } + } else { + result[0] += -198.82706671026182; + } + } else { + result[0] += -249.8175053383445; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 188))) { + if (UNLIKELY(false || (data[3].qvalue <= 98))) { + result[0] += -378.6732367385603; + } else { + result[0] += -109.64341915005078; + } + } else { + result[0] += -425.2420138470435; + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 220))) { + if (LIKELY(false || (data[0].qvalue <= 134))) { + if (LIKELY(false || (data[0].qvalue <= 72))) { + if (UNLIKELY(false || (data[0].qvalue <= 8))) { + result[0] += -532.059203726686; + } else { + result[0] += -372.9090262225109; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 82))) { + result[0] += -209.07044382236805; + } else { + result[0] += -400.00349533828; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (UNLIKELY(false || (data[7].qvalue <= 2))) { + result[0] += 412.17374136976844; + } else { + result[0] += -14.036003053192212; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 116))) { + result[0] += -154.44905243467502; + } else { + result[0] += -436.1506043761763; + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 116))) { + if (LIKELY(false || (data[0].qvalue <= 324))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (LIKELY(false || (data[0].qvalue <= 276))) { + result[0] += 180.81716708771435; + } else { + result[0] += 323.80885408996255; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 4))) { + result[0] += -804.687929563034; + } else { + if (LIKELY(false || (data[7].qvalue <= 144))) { + result[0] += 75.45041773548873; + } else { + result[0] += -308.6438664376922; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 198))) { + if (UNLIKELY(false || (data[2].qvalue <= 8))) { + if (UNLIKELY(false || (data[9].qvalue <= 60))) { + result[0] += -136.42148889369125; + } else { + result[0] += 347.35402497810156; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 40))) { + result[0] += 519.5328967780671; + } else { + result[0] += 317.09389087410756; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 442))) { + result[0] += -830.9681326018041; + } else { + result[0] += 776.5378702508172; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 392))) { + if (LIKELY(false || (data[1].qvalue <= 120))) { + if (UNLIKELY(false || (data[3].qvalue <= 96))) { + result[0] += -916.9602444683026; + } else { + if (LIKELY(false || (data[2].qvalue <= 190))) { + result[0] += 151.14410198089988; + } else { + result[0] += -534.6138410777346; + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 64))) { + result[0] += -20.321297171216408; + } else { + result[0] += -553.6897184690705; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 152))) { + if (LIKELY(false || (data[2].qvalue <= 214))) { + if (UNLIKELY(false || (data[8].qvalue <= 44))) { + result[0] += -94.32615099423269; + } else { + result[0] += 326.8245357585306; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -617.350459951314; + } else { + result[0] += 276.127751759582; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 442))) { + result[0] += -733.0199493164838; + } else { + if (LIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -116.42395706265687; + } else { + result[0] += 335.32040559141655; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 172))) { + if (UNLIKELY(false || (data[0].qvalue <= 78))) { + if (UNLIKELY(false || (data[0].qvalue <= 6))) { + result[0] += -493.1717265491526; + } else { + if (LIKELY(false || (data[1].qvalue <= 72))) { + result[0] += -307.1327071737299; + } else { + result[0] += -442.19784021780106; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 116))) { + if (LIKELY(false || (data[1].qvalue <= 72))) { + if (LIKELY(false || (data[0].qvalue <= 138))) { + result[0] += -167.9842807798525; + } else { + result[0] += -70.31231390063265; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 120))) { + result[0] += -254.86498043371085; + } else { + result[0] += -703.766647232002; + } + } + } else { + result[0] += -458.17666670208604; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 240))) { + if (LIKELY(false || (data[6].qvalue <= 72))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += 400.7108318320836; + } else { + if (LIKELY(false || (data[4].qvalue <= 128))) { + if (UNLIKELY(false || (data[4].qvalue <= 6))) { + result[0] += 227.72272060240076; + } else { + result[0] += 16.088229395762443; + } + } else { + result[0] += -679.3987618413844; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 146))) { + if (UNLIKELY(false || (data[3].qvalue <= 112))) { + result[0] += -287.0516773864561; + } else { + result[0] += -82.35735858878537; + } + } else { + result[0] += -473.44254046258266; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (UNLIKELY(false || (data[0].qvalue <= 326))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (UNLIKELY(false || (data[3].qvalue <= 34))) { + result[0] += 167.85776493357912; + } else { + result[0] += 297.3702646687886; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 116))) { + result[0] += 67.52512843170207; + } else { + result[0] += -189.67754872059376; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 40))) { + if (UNLIKELY(false || (data[3].qvalue <= 36))) { + result[0] += 818.1555179158704; + } else { + result[0] += 130.45031708535106; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 454))) { + result[0] += 327.4166938133286; + } else { + result[0] += -120.66748291707741; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 428))) { + if (LIKELY(false || (data[9].qvalue <= 68))) { + if (LIKELY(false || (data[0].qvalue <= 418))) { + result[0] += -535.7497746001039; + } else { + result[0] += -120.65339683228727; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 46))) { + result[0] += -1201.76536866533; + } else { + result[0] += 160.46676494029293; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 152))) { + if (LIKELY(false || (data[2].qvalue <= 214))) { + result[0] += 379.9797915421976; + } else { + result[0] += -12.579853903701677; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 452))) { + result[0] += -539.1034312262643; + } else { + result[0] += 118.1495781414129; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 210))) { + if (UNLIKELY(false || (data[0].qvalue <= 96))) { + if (UNLIKELY(false || (data[0].qvalue <= 12))) { + result[0] += -414.82218364010805; + } else { + if (LIKELY(false || (data[6].qvalue <= 100))) { + result[0] += -261.7354787568685; + } else { + result[0] += -436.9782510571122; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 126))) { + if (LIKELY(false || (data[6].qvalue <= 50))) { + if (UNLIKELY(false || (data[0].qvalue <= 158))) { + result[0] += -86.99960309582809; + } else { + result[0] += 36.01621466834996; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 54))) { + result[0] += -276.15518055633515; + } else { + result[0] += -117.47322502980383; + } + } + } else { + result[0] += -418.57103843448124; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 134))) { + if (UNLIKELY(false || (data[0].qvalue <= 300))) { + if (LIKELY(false || (data[6].qvalue <= 100))) { + if (UNLIKELY(false || (data[7].qvalue <= 24))) { + result[0] += 221.38591506553996; + } else { + if (UNLIKELY(false || (data[5].qvalue <= 34))) { + result[0] += -114.10943519348092; + } else { + result[0] += 124.41383507415142; + } + } + } else { + result[0] += -124.776312637069; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 40))) { + if (LIKELY(false || (data[0].qvalue <= 434))) { + if (LIKELY(false || (data[6].qvalue <= 134))) { + result[0] += 173.27180862569062; + } else { + result[0] += -230.0263780267813; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 90))) { + result[0] += 93.55822981381293; + } else { + result[0] += 531.9618157197915; + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 138))) { + if (UNLIKELY(false || (data[7].qvalue <= 12))) { + result[0] += 529.342886411045; + } else { + result[0] += 275.38241535187143; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 32))) { + result[0] += 168.94637195832345; + } else { + result[0] += -664.1470891916572; + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 442))) { + if (LIKELY(false || (data[6].qvalue <= 174))) { + if (LIKELY(false || (data[7].qvalue <= 198))) { + if (UNLIKELY(false || (data[9].qvalue <= 68))) { + result[0] += -259.076207652103; + } else { + result[0] += 97.97671807149003; + } + } else { + result[0] += -695.1606225313006; + } + } else { + result[0] += -796.0742621530271; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 180))) { + if (UNLIKELY(false || (data[2].qvalue <= 104))) { + if (LIKELY(false || (data[0].qvalue <= 464))) { + result[0] += -153.93531708038253; + } else { + result[0] += 448.69780310463796; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 216))) { + result[0] += 477.95650201824304; + } else { + result[0] += -28.61899971217077; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -909.6241555301945; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 470))) { + result[0] += -188.47445082878266; + } else { + result[0] += 377.35719169796516; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 172))) { + if (UNLIKELY(false || (data[0].qvalue <= 76))) { + if (UNLIKELY(false || (data[0].qvalue <= 8))) { + result[0] += -390.84856797995377; + } else { + if (LIKELY(false || (data[1].qvalue <= 64))) { + result[0] += -240.40122514681448; + } else { + result[0] += -347.4550413344793; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 134))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + result[0] += -39.25816550113254; + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[0].qvalue <= 142))) { + result[0] += -186.1240171676131; + } else { + result[0] += -98.0111804997198; + } + } else { + result[0] += -436.0524831294065; + } + } + } else { + result[0] += -471.83682540539394; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 238))) { + if (LIKELY(false || (data[1].qvalue <= 76))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += 348.9273763963965; + } else { + if (LIKELY(false || (data[7].qvalue <= 104))) { + result[0] += 30.53430231649784; + } else { + result[0] += -202.87449384945464; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 120))) { + if (UNLIKELY(false || (data[2].qvalue <= 4))) { + result[0] += -836.9509277269852; + } else { + if (UNLIKELY(false || (data[6].qvalue <= 22))) { + result[0] += -1508.3799402707123; + } else { + result[0] += -94.06253649645048; + } + } + } else { + result[0] += -348.5371075782411; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 126))) { + if (LIKELY(false || (data[0].qvalue <= 346))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (UNLIKELY(false || (data[0].qvalue <= 286))) { + result[0] += 162.75459965948465; + } else { + result[0] += 282.56625214886793; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 10))) { + result[0] += -440.06926455976475; + } else { + result[0] += 35.45780394100668; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 212))) { + if (UNLIKELY(false || (data[8].qvalue <= 72))) { + result[0] += 180.74606636101373; + } else { + result[0] += 317.0047465523858; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 438))) { + result[0] += -588.0578066297068; + } else { + result[0] += 433.5588979682989; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 422))) { + if (LIKELY(false || (data[1].qvalue <= 136))) { + if (LIKELY(false || (data[7].qvalue <= 192))) { + result[0] += 45.9325195881808; + } else { + result[0] += -679.3813065404768; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += 270.29103457509785; + } else { + result[0] += -517.5531688785617; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 118))) { + if (UNLIKELY(false || (data[10].qvalue <= 56))) { + result[0] += 117.54636858837371; + } else { + result[0] += 647.912058999977; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 464))) { + result[0] += -41.193603415175254; + } else { + result[0] += 296.9100448809772; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 222))) { + if (LIKELY(false || (data[0].qvalue <= 138))) { + if (UNLIKELY(false || (data[0].qvalue <= 58))) { + result[0] += -275.2115013121635; + } else { + if (LIKELY(false || (data[6].qvalue <= 116))) { + result[0] += -155.46609912259618; + } else { + result[0] += -389.7954080308127; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 100))) { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + result[0] += 384.7520448135499; + } else { + if (LIKELY(false || (data[9].qvalue <= 154))) { + if (UNLIKELY(false || (data[4].qvalue <= 6))) { + result[0] += 292.1491245847539; + } else { + result[0] += -30.543242192258454; + } + } else { + result[0] += -385.76320075157514; + } + } + } else { + result[0] += -224.14550590009833; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 68))) { + if (UNLIKELY(false || (data[0].qvalue <= 272))) { + result[0] += 106.3555671944755; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 8))) { + if (UNLIKELY(false || (data[9].qvalue <= 66))) { + if (UNLIKELY(false || (data[0].qvalue <= 410))) { + result[0] += -941.4297952572747; + } else { + result[0] += 56.28649828645251; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 8))) { + result[0] += 507.91991997031147; + } else { + result[0] += 35.20700760987173; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 348))) { + if (UNLIKELY(false || (data[7].qvalue <= 24))) { + result[0] += 324.4895405944488; + } else { + result[0] += 153.59057096574398; + } + } else { + if (LIKELY(false || (data[5].qvalue <= 60))) { + result[0] += 236.3224976398137; + } else { + result[0] += 444.2538183675668; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 372))) { + if (UNLIKELY(false || (data[9].qvalue <= 42))) { + if (UNLIKELY(false || (data[10].qvalue <= 64))) { + result[0] += 22.100327800787746; + } else { + result[0] += -285.1797635732232; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 182))) { + if (UNLIKELY(false || (data[3].qvalue <= 12))) { + result[0] += -916.6873662194294; + } else { + result[0] += 129.80380277726957; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 208))) { + result[0] += -121.90878827484735; + } else { + result[0] += -647.3935446930476; + } + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 10))) { + if (UNLIKELY(false || (data[0].qvalue <= 444))) { + if (LIKELY(false || (data[3].qvalue <= 168))) { + result[0] += -658.7332388174912; + } else { + result[0] += -100.44944033027124; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 174))) { + result[0] += 286.17922512478475; + } else { + result[0] += -166.4328133167238; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 94))) { + if (LIKELY(false || (data[3].qvalue <= 92))) { + result[0] += 56.97705669543027; + } else { + result[0] += -910.3064928974122; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 108))) { + result[0] += 459.24011774105895; + } else { + result[0] += 187.54878983259766; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 170))) { + if (UNLIKELY(false || (data[0].qvalue <= 80))) { + if (UNLIKELY(false || (data[11].qvalue <= 0))) { + if (UNLIKELY(false || (data[0].qvalue <= 18))) { + result[0] += -272.8015586187466; + } else { + result[0] += -158.01002915185765; + } + } else { + result[0] += -270.0109117909505; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 134))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + if (LIKELY(false || (data[5].qvalue <= 44))) { + result[0] += -47.655816285394785; + } else { + result[0] += 148.45176574086; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 48))) { + result[0] += -274.2183049595403; + } else { + result[0] += -120.42739359905393; + } + } + } else { + result[0] += -404.63297777523053; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 250))) { + if (LIKELY(false || (data[7].qvalue <= 86))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += 310.3087236621802; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 6))) { + result[0] += -261.86875131184433; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 30))) { + result[0] += 77.33362724018633; + } else { + result[0] += -9.835991514809445; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 178))) { + if (LIKELY(false || (data[10].qvalue <= 94))) { + if (UNLIKELY(false || (data[3].qvalue <= 18))) { + result[0] += -491.7147359309727; + } else { + result[0] += -26.424686375898183; + } + } else { + result[0] += -256.5814333154503; + } + } else { + result[0] += -401.38775248670413; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 134))) { + if (LIKELY(false || (data[0].qvalue <= 364))) { + if (UNLIKELY(false || (data[9].qvalue <= 54))) { + if (LIKELY(false || (data[8].qvalue <= 78))) { + result[0] += 24.807904435156793; + } else { + result[0] += -255.08075851432937; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 180))) { + result[0] += 171.3936836582958; + } else { + result[0] += -146.48986963094893; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 10))) { + if (UNLIKELY(false || (data[0].qvalue <= 446))) { + result[0] += -527.9370629462896; + } else { + result[0] += 311.74412615306; + } + } else { + if (UNLIKELY(false || (data[11].qvalue <= 0))) { + result[0] += 61.275444153721516; + } else { + result[0] += 266.8647138392115; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 464))) { + if (LIKELY(false || (data[2].qvalue <= 218))) { + if (UNLIKELY(false || (data[9].qvalue <= 2))) { + result[0] += -553.7238254263838; + } else { + result[0] += 30.964493797225533; + } + } else { + result[0] += -1039.405250832381; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 132))) { + if (LIKELY(false || (data[0].qvalue <= 470))) { + result[0] += 221.3473424703826; + } else { + result[0] += 578.2036295192621; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 470))) { + result[0] += -1067.1325237122935; + } else { + result[0] += -72.41418684700736; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 222))) { + if (LIKELY(false || (data[0].qvalue <= 120))) { + if (UNLIKELY(false || (data[0].qvalue <= 36))) { + result[0] += -245.63449384858325; + } else { + if (LIKELY(false || (data[1].qvalue <= 102))) { + result[0] += -141.07821937677804; + } else { + result[0] += -311.46095137887164; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 78))) { + result[0] += -18.828476188261668; + } else { + result[0] += -158.60479952768688; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[0].qvalue <= 334))) { + if (UNLIKELY(false || (data[6].qvalue <= 46))) { + if (UNLIKELY(false || (data[5].qvalue <= 28))) { + if (LIKELY(false || (data[2].qvalue <= 122))) { + result[0] += 85.17185179060556; + } else { + result[0] += -1488.3371391950336; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 120))) { + result[0] += 251.47188880049714; + } else { + result[0] += -337.59370375551794; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 176))) { + if (LIKELY(false || (data[7].qvalue <= 122))) { + result[0] += 64.67449717812703; + } else { + result[0] += -198.4096965608494; + } + } else { + result[0] += -109.54175072971603; + } + } + } else { + if (LIKELY(false || (data[5].qvalue <= 96))) { + if (UNLIKELY(false || (data[7].qvalue <= 64))) { + if (UNLIKELY(false || (data[2].qvalue <= 8))) { + result[0] += 25.027805981227203; + } else { + result[0] += 254.11335581266104; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 22))) { + result[0] += -327.20805261276865; + } else { + result[0] += 158.88433974650883; + } + } + } else { + result[0] += 635.3039299555891; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 422))) { + if (UNLIKELY(false || (data[5].qvalue <= 98))) { + if (LIKELY(false || (data[4].qvalue <= 116))) { + result[0] += -674.4791597614653; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 154))) { + result[0] += -621.8229750354634; + } else { + result[0] += 421.9420604815607; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 102))) { + if (LIKELY(false || (data[0].qvalue <= 390))) { + result[0] += 85.124602127931; + } else { + result[0] += 949.6197614387934; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 144))) { + result[0] += 251.2728036404487; + } else { + result[0] += -309.15702771287084; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 184))) { + if (LIKELY(false || (data[0].qvalue <= 460))) { + if (UNLIKELY(false || (data[10].qvalue <= 38))) { + result[0] += -633.4429247336111; + } else { + result[0] += 160.50792355093574; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 176))) { + result[0] += 479.12605526304554; + } else { + result[0] += 116.33385550515398; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -930.2431709848779; + } else { + if (LIKELY(false || (data[1].qvalue <= 156))) { + result[0] += 214.15884274598116; + } else { + result[0] += -277.81632186374355; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 168))) { + if (UNLIKELY(false || (data[0].qvalue <= 72))) { + if (LIKELY(false || (data[6].qvalue <= 88))) { + if (UNLIKELY(false || (data[0].qvalue <= 4))) { + result[0] += -294.9481294960577; + } else { + result[0] += -165.43274636129723; + } + } else { + result[0] += -283.98106032598395; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += 137.94773272215207; + } else { + result[0] += -74.90747793962899; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + result[0] += -146.0253896376931; + } else { + result[0] += -355.419967719613; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 268))) { + if (LIKELY(false || (data[1].qvalue <= 76))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += 312.51485279750943; + } else { + if (LIKELY(false || (data[2].qvalue <= 208))) { + if (UNLIKELY(false || (data[3].qvalue <= 34))) { + result[0] += -26.67715098965897; + } else { + result[0] += 59.16521507626189; + } + } else { + result[0] += -514.0817805406466; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 22))) { + result[0] += -1382.1700903888081; + } else { + if (LIKELY(false || (data[1].qvalue <= 148))) { + if (UNLIKELY(false || (data[6].qvalue <= 84))) { + result[0] += 2.2238582017569652; + } else { + result[0] += -149.07329069335975; + } + } else { + result[0] += -389.67704456086886; + } + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 134))) { + if (LIKELY(false || (data[2].qvalue <= 212))) { + if (UNLIKELY(false || (data[1].qvalue <= 8))) { + if (LIKELY(false || (data[0].qvalue <= 462))) { + result[0] += -139.5342233564615; + } else { + result[0] += 425.4054517675065; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 370))) { + result[0] += 119.1045005005382; + } else { + result[0] += 225.06622562801806; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 428))) { + if (UNLIKELY(false || (data[10].qvalue <= 116))) { + result[0] += -314.0145429164164; + } else { + result[0] += -791.7715009965185; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 156))) { + result[0] += 299.89845236333366; + } else { + result[0] += -121.70249001324471; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 438))) { + if (LIKELY(false || (data[6].qvalue <= 152))) { + if (UNLIKELY(false || (data[2].qvalue <= 42))) { + result[0] += 308.86651700943054; + } else { + result[0] += -123.15739696081778; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 162))) { + result[0] += -543.1460199892057; + } else { + result[0] += -268.8247484125225; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 170))) { + if (LIKELY(false || (data[5].qvalue <= 114))) { + result[0] += 219.69052341180478; + } else { + result[0] += 768.652154361283; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -330.30502416245446; + } else { + result[0] += 226.83056005764217; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 194))) { + if (UNLIKELY(false || (data[0].qvalue <= 94))) { + result[0] += -164.85997854401103; + } else { + if (LIKELY(false || (data[7].qvalue <= 134))) { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + if (LIKELY(false || (data[6].qvalue <= 32))) { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + result[0] += 272.37997423411696; + } else { + result[0] += -38.01170141177444; + } + } else { + result[0] += 179.2649038053183; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 40))) { + if (UNLIKELY(false || (data[10].qvalue <= 14))) { + result[0] += -447.1000423177568; + } else { + result[0] += -129.8926570286136; + } + } else { + result[0] += -58.31804328980124; + } + } + } else { + result[0] += -307.58593821488535; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (UNLIKELY(false || (data[0].qvalue <= 308))) { + if (LIKELY(false || (data[6].qvalue <= 116))) { + if (UNLIKELY(false || (data[7].qvalue <= 2))) { + result[0] += 484.75118404823917; + } else { + if (UNLIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 101.86133520052235; + } else { + result[0] += 18.262784206337113; + } + } + } else { + result[0] += -171.38940241656513; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 76))) { + if (LIKELY(false || (data[0].qvalue <= 420))) { + if (LIKELY(false || (data[4].qvalue <= 80))) { + result[0] += 164.27136795332518; + } else { + result[0] += 420.19522838999177; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 70))) { + result[0] += 288.60771862284395; + } else { + result[0] += -317.37860129812316; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 380))) { + if (UNLIKELY(false || (data[3].qvalue <= 112))) { + result[0] += -302.83673649889585; + } else { + result[0] += 54.622892990725916; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 98))) { + result[0] += -14.652198735183687; + } else { + result[0] += 188.62633009457414; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 410))) { + if (LIKELY(false || (data[1].qvalue <= 88))) { + if (UNLIKELY(false || (data[10].qvalue <= 68))) { + result[0] += -453.6048808127357; + } else { + result[0] += 39.27686618322581; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 20))) { + result[0] += 0.2890822714437353; + } else { + result[0] += -681.1445664481982; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 114))) { + if (LIKELY(false || (data[7].qvalue <= 198))) { + result[0] += 448.49867267672437; + } else { + if (LIKELY(false || (data[0].qvalue <= 446))) { + result[0] += -510.11854071816606; + } else { + result[0] += 725.828009865931; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + if (UNLIKELY(false || (data[10].qvalue <= 70))) { + result[0] += 82.63133907289594; + } else { + result[0] += -564.6009321848745; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 188))) { + result[0] += 485.04570202598234; + } else { + result[0] += -44.949579597135376; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 226))) { + if (LIKELY(false || (data[0].qvalue <= 140))) { + if (UNLIKELY(false || (data[6].qvalue <= 46))) { + result[0] += -89.22639695175398; + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + result[0] += -154.49086546432346; + } else { + result[0] += -329.5903225571293; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 126))) { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + result[0] += 318.48761171753574; + } else { + result[0] += -18.571568031593554; + } + } else { + result[0] += -220.49206840771905; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 202))) { + if (LIKELY(false || (data[1].qvalue <= 152))) { + if (LIKELY(false || (data[0].qvalue <= 366))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (LIKELY(false || (data[8].qvalue <= 120))) { + result[0] += 150.42799764081784; + } else { + result[0] += -76.38500429830752; + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 56))) { + result[0] += -211.0027314849032; + } else { + result[0] += 24.230230872826798; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 164))) { + if (LIKELY(false || (data[8].qvalue <= 142))) { + result[0] += 132.68242055886515; + } else { + result[0] += -882.8096755983888; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 192))) { + result[0] += 334.11768457333994; + } else { + result[0] += -53.48084930478878; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 450))) { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += 233.21157693531032; + } else { + if (LIKELY(false || (data[10].qvalue <= 138))) { + result[0] += -582.6982780907397; + } else { + result[0] += 0.7508374869349193; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 174))) { + if (UNLIKELY(false || (data[2].qvalue <= 66))) { + result[0] += 844.2241348987232; + } else { + result[0] += 166.47618379149745; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -770.8958350747589; + } else { + result[0] += 106.39982734044628; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 424))) { + if (UNLIKELY(false || (data[10].qvalue <= 116))) { + if (LIKELY(false || (data[10].qvalue <= 114))) { + result[0] += -318.826948941992; + } else { + result[0] += 300.5051761631031; + } + } else { + result[0] += -663.2498735346456; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 68))) { + if (LIKELY(false || (data[7].qvalue <= 176))) { + result[0] += 330.2362671942275; + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + result[0] += -261.9859689644222; + } else { + result[0] += 470.337710070637; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + if (UNLIKELY(false || (data[8].qvalue <= 134))) { + result[0] += 52.55390876938948; + } else { + result[0] += -820.466862953795; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 172))) { + result[0] += 678.0504747681679; + } else { + result[0] += -30.65402416986749; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 230))) { + if (LIKELY(false || (data[0].qvalue <= 146))) { + if (LIKELY(false || (data[7].qvalue <= 124))) { + if (UNLIKELY(false || (data[0].qvalue <= 40))) { + result[0] += -163.25748720727836; + } else { + result[0] += -83.1950205740847; + } + } else { + result[0] += -269.08136712131557; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 54))) { + result[0] += -164.01997292662278; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += 228.21827507549563; + } else { + result[0] += -9.402288377382439; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 142))) { + if (UNLIKELY(false || (data[7].qvalue <= 16))) { + if (LIKELY(false || (data[6].qvalue <= 30))) { + if (UNLIKELY(false || (data[7].qvalue <= 4))) { + result[0] += 381.64939051951137; + } else { + if (LIKELY(false || (data[0].qvalue <= 302))) { + result[0] += 58.00057357423904; + } else { + result[0] += 223.74792550499748; + } + } + } else { + result[0] += 454.5662709049777; + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 36))) { + if (LIKELY(false || (data[8].qvalue <= 128))) { + if (LIKELY(false || (data[0].qvalue <= 378))) { + result[0] += -93.01200660095587; + } else { + result[0] += 134.32252549902134; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 444))) { + result[0] += -2078.6414691042096; + } else { + result[0] += -159.18640228976722; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 32))) { + if (UNLIKELY(false || (data[0].qvalue <= 396))) { + result[0] += -163.73514121181952; + } else { + result[0] += 100.75922992115221; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 360))) { + result[0] += 84.30972820399808; + } else { + result[0] += 197.85022347944687; + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 442))) { + if (LIKELY(false || (data[7].qvalue <= 198))) { + if (UNLIKELY(false || (data[6].qvalue <= 114))) { + if (LIKELY(false || (data[0].qvalue <= 406))) { + result[0] += -124.87742350731226; + } else { + result[0] += 371.4284844619934; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 130))) { + result[0] += -1022.9075285275169; + } else { + result[0] += -154.11889775108097; + } + } + } else { + result[0] += -609.5631862224477; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 180))) { + if (UNLIKELY(false || (data[2].qvalue <= 104))) { + if (LIKELY(false || (data[0].qvalue <= 464))) { + result[0] += -172.46788335138166; + } else { + result[0] += 267.92756125694655; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 216))) { + result[0] += 344.21102212490365; + } else { + result[0] += -63.20922723234142; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (UNLIKELY(false || (data[4].qvalue <= 88))) { + result[0] += -98.6686560628389; + } else { + result[0] += -824.7416534209821; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 162))) { + result[0] += 423.0262534565941; + } else { + result[0] += -172.900534785997; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 164))) { + if (UNLIKELY(false || (data[7].qvalue <= 36))) { + if (UNLIKELY(false || (data[0].qvalue <= 22))) { + result[0] += -151.00440148400935; + } else { + result[0] += -40.118590528856686; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 134))) { + result[0] += -118.83158145049806; + } else { + result[0] += -265.5488061301905; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (UNLIKELY(false || (data[0].qvalue <= 276))) { + if (LIKELY(false || (data[1].qvalue <= 76))) { + if (UNLIKELY(false || (data[7].qvalue <= 2))) { + result[0] += 337.5770961546419; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 34))) { + result[0] += -45.53149621844132; + } else { + result[0] += 57.69099405123824; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 22))) { + result[0] += -1262.4368580820865; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 4))) { + result[0] += -499.135631046245; + } else { + result[0] += -51.130463115279184; + } + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 10))) { + if (UNLIKELY(false || (data[0].qvalue <= 446))) { + if (LIKELY(false || (data[10].qvalue <= 132))) { + result[0] += -452.52727046733617; + } else { + result[0] += 207.2426999628595; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 186))) { + result[0] += 147.30212851601848; + } else { + result[0] += -378.60903812177116; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 8))) { + if (LIKELY(false || (data[7].qvalue <= 116))) { + result[0] += 60.647838286510584; + } else { + result[0] += -403.6642612098349; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 144))) { + result[0] += 97.61644906336824; + } else { + result[0] += 243.6057368289787; + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 440))) { + if (LIKELY(false || (data[10].qvalue <= 116))) { + if (LIKELY(false || (data[0].qvalue <= 412))) { + if (LIKELY(false || (data[10].qvalue <= 114))) { + result[0] += -372.46208551501405; + } else { + result[0] += 150.12385303560893; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 154))) { + result[0] += 378.2084024000228; + } else { + result[0] += -115.22596011254524; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 104))) { + result[0] += -1013.8954317267925; + } else { + result[0] += -458.7466793813837; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 162))) { + if (LIKELY(false || (data[0].qvalue <= 460))) { + if (UNLIKELY(false || (data[10].qvalue <= 118))) { + result[0] += 573.7495286165021; + } else { + result[0] += -39.64820127548395; + } + } else { + result[0] += 747.9256037479381; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 464))) { + if (UNLIKELY(false || (data[2].qvalue <= 218))) { + result[0] += -322.32729930754635; + } else { + result[0] += -889.5131323414915; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + result[0] += -49.77314604177496; + } else { + result[0] += 452.922151113344; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 230))) { + if (UNLIKELY(false || (data[0].qvalue <= 100))) { + result[0] += -110.27471806521078; + } else { + if (UNLIKELY(false || (data[9].qvalue <= 54))) { + if (UNLIKELY(false || (data[1].qvalue <= 36))) { + result[0] += 405.5242126958143; + } else { + result[0] += -170.43432845409902; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + result[0] += 256.63050409714157; + } else { + if (LIKELY(false || (data[9].qvalue <= 154))) { + if (UNLIKELY(false || (data[4].qvalue <= 6))) { + result[0] += 199.22002184764415; + } else { + result[0] += -21.456223281242632; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 20))) { + result[0] += -394.6713467594481; + } else { + result[0] += 400.4595635278145; + } + } + } + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (UNLIKELY(false || (data[9].qvalue <= 2))) { + if (LIKELY(false || (data[0].qvalue <= 466))) { + if (LIKELY(false || (data[6].qvalue <= 170))) { + if (UNLIKELY(false || (data[1].qvalue <= 146))) { + result[0] += -1171.2407195011317; + } else { + result[0] += 62.25614012625213; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 462))) { + result[0] += -747.9426165897981; + } else { + result[0] += -245.0583143986272; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 184))) { + result[0] += 389.8618473333866; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -714.1912396897699; + } else { + result[0] += 105.56232441101614; + } + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 30))) { + if (LIKELY(false || (data[6].qvalue <= 152))) { + if (LIKELY(false || (data[0].qvalue <= 452))) { + result[0] += 8.267421890159907; + } else { + result[0] += 532.9815498007549; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 450))) { + result[0] += -998.5163824104064; + } else { + result[0] += 67.53749679257139; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 38))) { + if (LIKELY(false || (data[0].qvalue <= 314))) { + result[0] += 128.68056584630503; + } else { + result[0] += 304.65594504617775; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 378))) { + result[0] += 38.864839831291896; + } else { + result[0] += 132.95011491298308; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 422))) { + if (LIKELY(false || (data[10].qvalue <= 116))) { + result[0] += -200.50051859519212; + } else { + result[0] += -570.0695420065967; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 76))) { + if (LIKELY(false || (data[2].qvalue <= 214))) { + result[0] += 284.7667993502012; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 452))) { + result[0] += -439.2896395697744; + } else { + result[0] += 195.2703219505385; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 448))) { + result[0] += -719.4958031369861; + } else { + if (LIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -48.870417355715304; + } else { + result[0] += 558.0516523176542; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 158))) { + if (LIKELY(false || (data[1].qvalue <= 102))) { + if (UNLIKELY(false || (data[0].qvalue <= 28))) { + result[0] += -132.8644217361974; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 20))) { + if (LIKELY(false || (data[5].qvalue <= 44))) { + if (UNLIKELY(false || (data[6].qvalue <= 0))) { + result[0] += 184.7567664677024; + } else { + result[0] += -31.01544671430151; + } + } else { + result[0] += 93.76937878382813; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 158))) { + result[0] += -70.3541078253051; + } else { + result[0] += -256.18565932246213; + } + } + } + } else { + result[0] += -186.9601821997848; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 192))) { + if (UNLIKELY(false || (data[0].qvalue <= 272))) { + if (UNLIKELY(false || (data[7].qvalue <= 22))) { + if (LIKELY(false || (data[6].qvalue <= 32))) { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + result[0] += 301.99251039980805; + } else { + result[0] += 8.317443045497088; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 285.9447580619998; + } else { + result[0] += -199.02985809976943; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 34))) { + if (UNLIKELY(false || (data[7].qvalue <= 28))) { + result[0] += -757.7155546033136; + } else { + result[0] += -100.8798688536487; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 54))) { + result[0] += -108.18906289397617; + } else { + result[0] += 16.427955171508895; + } + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 32))) { + if (LIKELY(false || (data[0].qvalue <= 450))) { + if (LIKELY(false || (data[6].qvalue <= 168))) { + result[0] += -31.079261573945725; + } else { + result[0] += -439.24160085690585; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 176))) { + result[0] += 196.17828368025368; + } else { + result[0] += -96.92802902335563; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 8))) { + if (LIKELY(false || (data[7].qvalue <= 150))) { + result[0] += 5.472886535824474; + } else { + result[0] += -322.6822453952255; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 104))) { + result[0] += 71.15954913651206; + } else { + result[0] += 170.40770380917564; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 434))) { + if (LIKELY(false || (data[1].qvalue <= 98))) { + result[0] += -431.7182112009705; + } else { + result[0] += -761.9070576282425; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 174))) { + if (LIKELY(false || (data[0].qvalue <= 450))) { + if (LIKELY(false || (data[3].qvalue <= 170))) { + result[0] += 223.6763270971365; + } else { + result[0] += -863.1311602081029; + } + } else { + result[0] += 633.5684765097419; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 464))) { + result[0] += -822.025766073113; + } else { + if (LIKELY(false || (data[8].qvalue <= 144))) { + result[0] += -357.65137650350306; + } else { + result[0] += 161.10489597769953; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 230))) { + if (UNLIKELY(false || (data[0].qvalue <= 94))) { + result[0] += -92.57565870144303; + } else { + if (LIKELY(false || (data[7].qvalue <= 142))) { + result[0] += -18.109772204939024; + } else { + result[0] += -209.49572955406617; + } + } + } else { + if (LIKELY(false || (data[10].qvalue <= 116))) { + if (LIKELY(false || (data[1].qvalue <= 152))) { + if (UNLIKELY(false || (data[10].qvalue <= 30))) { + if (LIKELY(false || (data[0].qvalue <= 462))) { + if (LIKELY(false || (data[5].qvalue <= 102))) { + result[0] += 3.2575921560660412; + } else { + result[0] += -901.3597327991677; + } + } else { + result[0] += 507.2677983999125; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 128))) { + if (UNLIKELY(false || (data[5].qvalue <= 12))) { + result[0] += -59.086766158297294; + } else { + result[0] += 112.28787145525055; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 58))) { + result[0] += 323.6859359964492; + } else { + result[0] += -58.36682033574347; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 460))) { + if (LIKELY(false || (data[2].qvalue <= 84))) { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += 300.9633139092185; + } else { + result[0] += -192.01700771623888; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 166))) { + result[0] += -1598.762896930197; + } else { + result[0] += -603.9295642909062; + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 16))) { + if (UNLIKELY(false || (data[6].qvalue <= 174))) { + result[0] += 461.56736902180194; + } else { + result[0] += 8.737578542205178; + } + } else { + result[0] += -643.983821829543; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 440))) { + if (LIKELY(false || (data[6].qvalue <= 104))) { + if (UNLIKELY(false || (data[2].qvalue <= 148))) { + if (LIKELY(false || (data[2].qvalue <= 134))) { + result[0] += 179.91548506297573; + } else { + result[0] += -912.5235714991372; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 174))) { + result[0] += 301.8534329221878; + } else { + result[0] += 38.18076419361558; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 94))) { + if (LIKELY(false || (data[0].qvalue <= 406))) { + result[0] += -361.8709774599796; + } else { + result[0] += 270.3263916029786; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 6))) { + result[0] += 104.81627908969739; + } else { + result[0] += -535.2156269218489; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 170))) { + if (LIKELY(false || (data[4].qvalue <= 122))) { + if (UNLIKELY(false || (data[7].qvalue <= 114))) { + result[0] += 594.5833564826081; + } else { + result[0] += 64.11583431949299; + } + } else { + result[0] += 652.8845003559351; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -763.9957389916799; + } else { + if (UNLIKELY(false || (data[4].qvalue <= 102))) { + result[0] += 315.48470650562535; + } else { + result[0] += -203.67379945157032; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 154))) { + if (UNLIKELY(false || (data[4].qvalue <= 36))) { + if (UNLIKELY(false || (data[0].qvalue <= 12))) { + result[0] += -126.5718042782856; + } else { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += 111.5057760402395; + } else { + result[0] += -28.4538638965687; + } + } else { + result[0] += -313.9852425571987; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 142))) { + if (UNLIKELY(false || (data[9].qvalue <= 48))) { + result[0] += -159.27654309724835; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 54))) { + result[0] += -114.2328146382158; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 18))) { + result[0] += -169.53442010531597; + } else { + result[0] += -51.819614520602755; + } + } + } + } else { + result[0] += -237.49538829330947; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 192))) { + if (UNLIKELY(false || (data[7].qvalue <= 16))) { + if (LIKELY(false || (data[5].qvalue <= 46))) { + if (LIKELY(false || (data[0].qvalue <= 290))) { + if (UNLIKELY(false || (data[7].qvalue <= 2))) { + result[0] += 287.9491607426837; + } else { + result[0] += 22.668345828570306; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 0))) { + result[0] += -77.53473409498233; + } else { + result[0] += 204.6386641118168; + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 16))) { + result[0] += 593.7874627921306; + } else { + result[0] += 312.8524644986319; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 42))) { + if (UNLIKELY(false || (data[6].qvalue <= 24))) { + if (LIKELY(false || (data[2].qvalue <= 122))) { + result[0] += -142.83280406635228; + } else { + result[0] += -693.2501230727516; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 122))) { + result[0] += 37.74683956549833; + } else { + result[0] += -198.748856011046; + } + } + } else { + if (LIKELY(false || (data[10].qvalue <= 108))) { + if (UNLIKELY(false || (data[2].qvalue <= 8))) { + result[0] += -89.22275972296083; + } else { + result[0] += 86.32327653910806; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 442))) { + result[0] += -55.02130370736227; + } else { + result[0] += 177.35435049468094; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 432))) { + if (UNLIKELY(false || (data[2].qvalue <= 74))) { + result[0] += -882.5317237443489; + } else { + result[0] += -481.30010328266326; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 132))) { + if (LIKELY(false || (data[0].qvalue <= 450))) { + if (UNLIKELY(false || (data[7].qvalue <= 198))) { + result[0] += 647.320083633933; + } else { + result[0] += 38.11111140878454; + } + } else { + result[0] += 745.2928537341078; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -789.9197396160231; + } else { + if (UNLIKELY(false || (data[8].qvalue <= 144))) { + result[0] += -357.5527732739339; + } else { + result[0] += 102.50578713894222; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 232))) { + if (LIKELY(false || (data[6].qvalue <= 116))) { + if (UNLIKELY(false || (data[0].qvalue <= 62))) { + result[0] += -81.42467173085487; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 16))) { + if (LIKELY(false || (data[5].qvalue <= 48))) { + result[0] += 9.502633625283542; + } else { + result[0] += 279.42600545633803; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 28))) { + if (UNLIKELY(false || (data[6].qvalue <= 18))) { + result[0] += -361.821022373354; + } else { + result[0] += -88.28610792754384; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 120))) { + result[0] += -20.102121037240444; + } else { + result[0] += -420.12674867604005; + } + } + } + } + } else { + result[0] += -159.62279487679336; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 38))) { + if (LIKELY(false || (data[10].qvalue <= 124))) { + if (UNLIKELY(false || (data[6].qvalue <= 10))) { + if (LIKELY(false || (data[7].qvalue <= 18))) { + result[0] += 88.01385578444962; + } else { + if (UNLIKELY(false || (data[6].qvalue <= 4))) { + result[0] += 493.08930447766016; + } else { + result[0] += -285.19813158141295; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 82))) { + result[0] += 106.27335201243972; + } else { + result[0] += 226.93623921451922; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 50))) { + if (LIKELY(false || (data[0].qvalue <= 412))) { + result[0] += -1343.712162589175; + } else { + result[0] += 132.58954443144634; + } + } else { + result[0] += 91.6622701261032; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 44))) { + if (LIKELY(false || (data[3].qvalue <= 38))) { + if (LIKELY(false || (data[4].qvalue <= 84))) { + if (LIKELY(false || (data[0].qvalue <= 368))) { + result[0] += -236.33002335788538; + } else { + result[0] += 20.986096347583267; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 128))) { + result[0] += 344.9152673102923; + } else { + result[0] += -105.45712817788554; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += 328.6203717386541; + } else { + if (LIKELY(false || (data[0].qvalue <= 448))) { + result[0] += -650.199452548778; + } else { + result[0] += -47.9905026661036; + } + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 132))) { + if (LIKELY(false || (data[0].qvalue <= 366))) { + if (LIKELY(false || (data[2].qvalue <= 180))) { + result[0] += 42.03195219241939; + } else { + result[0] += -158.93413279533422; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 74))) { + result[0] += -34.071317241183344; + } else { + result[0] += 156.0271506153512; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 440))) { + if (UNLIKELY(false || (data[6].qvalue <= 102))) { + result[0] += 133.87095453891186; + } else { + result[0] += -228.7502100788784; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 108))) { + result[0] += 347.50113932020776; + } else { + result[0] += 14.191860694706207; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 230))) { + if (UNLIKELY(false || (data[6].qvalue <= 46))) { + if (UNLIKELY(false || (data[3].qvalue <= 34))) { + if (LIKELY(false || (data[2].qvalue <= 122))) { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + result[0] += 177.32409766602134; + } else { + if (UNLIKELY(false || (data[10].qvalue <= 10))) { + result[0] += -222.24918504973667; + } else { + result[0] += -35.30855443997724; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 4))) { + result[0] += -176.9665709227076; + } else { + result[0] += -753.9332796594691; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 116))) { + result[0] += -28.342368225619726; + } else { + if (UNLIKELY(false || (data[4].qvalue <= 8))) { + result[0] += 421.668098564295; + } else { + if (LIKELY(false || (data[3].qvalue <= 70))) { + result[0] += 43.5107313173905; + } else { + result[0] += 227.90844897767985; + } + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + result[0] += -57.17411824360336; + } else { + result[0] += -189.687353931762; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 202))) { + if (UNLIKELY(false || (data[3].qvalue <= 0))) { + if (LIKELY(false || (data[4].qvalue <= 54))) { + if (LIKELY(false || (data[9].qvalue <= 156))) { + result[0] += 143.81130747694493; + } else { + if (LIKELY(false || (data[0].qvalue <= 378))) { + result[0] += -412.9308129291959; + } else { + result[0] += 174.61191720224775; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 422))) { + if (LIKELY(false || (data[9].qvalue <= 88))) { + result[0] += -729.9085027051653; + } else { + result[0] += -1582.3957851064256; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 82))) { + result[0] += 255.17536551772466; + } else { + result[0] += -411.7747098985702; + } + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 0))) { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -687.4964190637061; + } else { + if (UNLIKELY(false || (data[1].qvalue <= 156))) { + result[0] += 579.2665867229997; + } else { + result[0] += -202.15282940470115; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 378))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + result[0] += 87.73491261546084; + } else { + result[0] += -32.02969863953809; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 166))) { + result[0] += 64.28717265542389; + } else { + result[0] += 229.17843828172778; + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (LIKELY(false || (data[6].qvalue <= 176))) { + if (LIKELY(false || (data[0].qvalue <= 442))) { + if (UNLIKELY(false || (data[10].qvalue <= 116))) { + result[0] += -44.67740033036628; + } else { + result[0] += -394.97654476097733; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 222))) { + result[0] += 206.95175345062236; + } else { + result[0] += -422.41503848096585; + } + } + } else { + result[0] += -637.136586228852; + } + } else { + result[0] += 376.9378106237276; + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 162))) { + if (LIKELY(false || (data[1].qvalue <= 64))) { + if (UNLIKELY(false || (data[0].qvalue <= 4))) { + result[0] += -145.63790783710485; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += 92.69381886875624; + } else { + if (LIKELY(false || (data[2].qvalue <= 200))) { + result[0] += -29.52565796333189; + } else { + result[0] += -264.4833581843737; + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 80))) { + result[0] += -65.00862983366554; + } else { + result[0] += -128.3574858335414; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 192))) { + if (LIKELY(false || (data[10].qvalue <= 116))) { + if (LIKELY(false || (data[0].qvalue <= 334))) { + if (LIKELY(false || (data[7].qvalue <= 44))) { + if (UNLIKELY(false || (data[3].qvalue <= 34))) { + result[0] += 2.638363742314313; + } else { + result[0] += 90.02482928313995; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 46))) { + result[0] += -159.46662656973402; + } else { + result[0] += -6.479090823017612; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 10))) { + if (LIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -181.28880117455623; + } else { + result[0] += 348.385125441513; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 8))) { + result[0] += -95.60162541279402; + } else { + result[0] += 108.90915732052065; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 448))) { + if (LIKELY(false || (data[6].qvalue <= 104))) { + if (UNLIKELY(false || (data[8].qvalue <= 92))) { + result[0] += -264.2515788707746; + } else { + result[0] += 101.55470547812884; + } + } else { + if (LIKELY(false || (data[8].qvalue <= 148))) { + result[0] += -178.22322886037145; + } else { + result[0] += -572.0017383471462; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 176))) { + if (LIKELY(false || (data[2].qvalue <= 216))) { + result[0] += 360.0600154694514; + } else { + result[0] += -2.277701777775852; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + result[0] += -592.4446434254904; + } else { + result[0] += 311.89032842128967; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 434))) { + if (LIKELY(false || (data[1].qvalue <= 98))) { + if (LIKELY(false || (data[3].qvalue <= 170))) { + result[0] += -269.5216917245177; + } else { + result[0] += -763.2129798350616; + } + } else { + result[0] += -630.6861669974678; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 132))) { + if (LIKELY(false || (data[0].qvalue <= 452))) { + if (UNLIKELY(false || (data[1].qvalue <= 98))) { + result[0] += 491.16969306572764; + } else { + result[0] += -1.8378346933226348; + } + } else { + result[0] += 735.4661909012816; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -714.8393720262166; + } else { + if (LIKELY(false || (data[9].qvalue <= 124))) { + result[0] += -185.85739524553466; + } else { + result[0] += 534.3912650447504; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 234))) { + if (UNLIKELY(false || (data[6].qvalue <= 46))) { + if (UNLIKELY(false || (data[3].qvalue <= 34))) { + if (LIKELY(false || (data[2].qvalue <= 122))) { + if (LIKELY(false || (data[7].qvalue <= 34))) { + result[0] += -17.516199145518204; + } else { + if (LIKELY(false || (data[5].qvalue <= 40))) { + result[0] += -106.45476753502933; + } else { + result[0] += -903.8252212915366; + } + } + } else { + result[0] += -429.58429904156355; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 108))) { + result[0] += -24.64488868029451; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 6))) { + result[0] += 442.263546116001; + } else { + result[0] += 55.12907189754925; + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + result[0] += -46.866944249531215; + } else { + result[0] += -159.04948180790302; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (UNLIKELY(false || (data[3].qvalue <= 0))) { + if (LIKELY(false || (data[7].qvalue <= 110))) { + result[0] += -7.415599980597751; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 420))) { + if (LIKELY(false || (data[2].qvalue <= 196))) { + result[0] += -1399.7966503370856; + } else { + result[0] += -610.3226946773707; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 96))) { + result[0] += 354.8632074839545; + } else { + result[0] += -377.08280725284146; + } + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 12))) { + if (LIKELY(false || (data[3].qvalue <= 54))) { + if (LIKELY(false || (data[0].qvalue <= 312))) { + result[0] += 78.41314807466219; + } else { + result[0] += 229.81473981996055; + } + } else { + result[0] += 656.2836431782829; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 370))) { + if (UNLIKELY(false || (data[6].qvalue <= 62))) { + result[0] += 59.2010780966252; + } else { + result[0] += -33.600013356306874; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 10))) { + result[0] += -40.555513916002155; + } else { + result[0] += 98.08389359507521; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 412))) { + if (UNLIKELY(false || (data[8].qvalue <= 136))) { + if (LIKELY(false || (data[8].qvalue <= 122))) { + result[0] += -319.74524116929115; + } else { + result[0] += -767.7796729203433; + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 82))) { + result[0] += 122.08054350335395; + } else { + result[0] += -301.9722820682188; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 114))) { + if (LIKELY(false || (data[7].qvalue <= 198))) { + result[0] += 313.9124097005065; + } else { + result[0] += -8.79354690498097; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (LIKELY(false || (data[6].qvalue <= 180))) { + result[0] += -72.7211246346664; + } else { + result[0] += -510.6446546492408; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 188))) { + result[0] += 560.0836128367328; + } else { + result[0] += 17.985013800397187; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 148))) { + if (UNLIKELY(false || (data[4].qvalue <= 36))) { + if (UNLIKELY(false || (data[0].qvalue <= 2))) { + result[0] += -141.96709416955443; + } else { + result[0] += -15.705324092558222; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 152))) { + result[0] += -61.382323220403975; + } else { + result[0] += -187.5709723722473; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (LIKELY(false || (data[0].qvalue <= 352))) { + if (UNLIKELY(false || (data[6].qvalue <= 46))) { + if (UNLIKELY(false || (data[3].qvalue <= 34))) { + if (LIKELY(false || (data[7].qvalue <= 16))) { + result[0] += 44.92602753182352; + } else { + result[0] += -211.1546710682142; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 70))) { + result[0] += 84.82973193387151; + } else { + result[0] += 297.5136117027242; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 54))) { + if (LIKELY(false || (data[8].qvalue <= 80))) { + result[0] += 17.890869053801463; + } else { + result[0] += -233.23923140903472; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 42))) { + result[0] += -115.18248063786203; + } else { + result[0] += 19.654733381799502; + } + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 38))) { + if (UNLIKELY(false || (data[8].qvalue <= 48))) { + if (UNLIKELY(false || (data[6].qvalue <= 108))) { + result[0] += 73.48571042663086; + } else { + result[0] += -228.89571182954265; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 412))) { + result[0] += -172.0892135464257; + } else { + result[0] += 115.89684884981358; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 74))) { + if (UNLIKELY(false || (data[2].qvalue <= 2))) { + result[0] += -192.70183326405345; + } else { + result[0] += 228.22523400305982; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 164))) { + result[0] += -14.495336023506457; + } else { + result[0] += 164.58338032467216; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 418))) { + if (UNLIKELY(false || (data[6].qvalue <= 110))) { + result[0] += 92.44304518392212; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 142))) { + result[0] += -549.5535909490123; + } else { + result[0] += -283.7280102420611; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 170))) { + if (UNLIKELY(false || (data[3].qvalue <= 110))) { + if (UNLIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -751.8919251402435; + } else { + result[0] += 185.12085880810656; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 442))) { + result[0] += 106.48103521611722; + } else { + result[0] += 402.22669875787847; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 460))) { + if (UNLIKELY(false || (data[4].qvalue <= 14))) { + result[0] += -36.04581623793381; + } else { + result[0] += -576.7489848172199; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 204))) { + result[0] += 811.3997608577275; + } else { + result[0] += 54.07893988355018; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 190))) { + if (LIKELY(false || (data[1].qvalue <= 118))) { + if (UNLIKELY(false || (data[0].qvalue <= 40))) { + result[0] += -66.6266648878957; + } else { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (UNLIKELY(false || (data[7].qvalue <= 0))) { + result[0] += 218.52501238124137; + } else { + if (LIKELY(false || (data[7].qvalue <= 184))) { + result[0] += -13.543587342327356; + } else { + result[0] += -215.10060994815572; + } + } + } else { + result[0] += -235.9788695560659; + } + } + } else { + result[0] += -124.00456972107527; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 192))) { + if (LIKELY(false || (data[10].qvalue <= 122))) { + if (UNLIKELY(false || (data[10].qvalue <= 30))) { + if (LIKELY(false || (data[0].qvalue <= 462))) { + if (LIKELY(false || (data[5].qvalue <= 102))) { + result[0] += -18.111064688282102; + } else { + result[0] += -830.4402494030709; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 114))) { + result[0] += 549.3375303639418; + } else { + result[0] += -471.1944868822796; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 38))) { + if (LIKELY(false || (data[0].qvalue <= 306))) { + result[0] += 58.15499047997176; + } else { + result[0] += 202.68651234520863; + } + } else { + if (LIKELY(false || (data[5].qvalue <= 84))) { + result[0] += 5.292449943571185; + } else { + result[0] += 88.29265314882701; + } + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 136))) { + if (LIKELY(false || (data[0].qvalue <= 414))) { + if (LIKELY(false || (data[1].qvalue <= 128))) { + result[0] += -1380.2032030523546; + } else { + result[0] += -290.5204067553985; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 428))) { + result[0] += -114.53892152149231; + } else { + result[0] += 514.5293420314608; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 58))) { + if (UNLIKELY(false || (data[4].qvalue <= 48))) { + result[0] += -61.89760896238173; + } else { + result[0] += 238.5724533454146; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 440))) { + result[0] += -129.87454115256398; + } else { + result[0] += 80.61545671612187; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 436))) { + if (UNLIKELY(false || (data[0].qvalue <= 346))) { + result[0] += -205.98214313987833; + } else { + result[0] += -501.7217730494733; + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 94))) { + if (UNLIKELY(false || (data[9].qvalue <= 74))) { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -1647.808151855469; + } else { + result[0] += -111.63345227808891; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 448))) { + result[0] += 135.65529729266765; + } else { + result[0] += 542.5839931646244; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -605.3494305872034; + } else { + if (LIKELY(false || (data[7].qvalue <= 200))) { + result[0] += 23.37492749029042; + } else { + result[0] += -504.33767145881876; + } + } + } + } + } + } + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + if (LIKELY(false || (data[3].qvalue <= 134))) { + if (UNLIKELY(false || (data[8].qvalue <= 6))) { + if (UNLIKELY(false || (data[2].qvalue <= 28))) { + result[0] += 147.6732444791957; + } else { + if (LIKELY(false || (data[2].qvalue <= 46))) { + result[0] += -10.796001760756946; + } else { + result[0] += 119.76151703653773; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 16))) { + result[0] += -80.64564777701082; + } else { + result[0] += -137.81918275187488; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 188))) { + result[0] += -301.13288115967265; + } else { + result[0] += -677.8347078414617; + } + } + } else { + if (LIKELY(false || (data[3].qvalue <= 102))) { + if (LIKELY(false || (data[7].qvalue <= 48))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + if (LIKELY(false || (data[1].qvalue <= 66))) { + result[0] += 177.5420680296681; + } else { + result[0] += -218.59350937699685; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 20))) { + result[0] += -72.74967625197816; + } else { + result[0] += 20.742745610897746; + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 84))) { + if (LIKELY(false || (data[4].qvalue <= 78))) { + result[0] += -90.48006952525736; + } else { + result[0] += -828.7521117629706; + } + } else { + if (LIKELY(false || (data[5].qvalue <= 66))) { + result[0] += 120.02128594767765; + } else { + result[0] += -101.64457682331833; + } + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 32))) { + if (UNLIKELY(false || (data[8].qvalue <= 48))) { + if (LIKELY(false || (data[8].qvalue <= 24))) { + result[0] += -29.977235441363906; + } else { + result[0] += -517.2676348797457; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 138))) { + result[0] += 79.82233930374132; + } else { + result[0] += -65.66862801060336; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 176))) { + if (UNLIKELY(false || (data[2].qvalue <= 4))) { + result[0] += -246.17761450724754; + } else { + result[0] += 128.63335245716783; + } + } else { + if (LIKELY(false || (data[10].qvalue <= 134))) { + result[0] += 25.606045866379198; + } else { + result[0] += -125.59366437898994; + } + } + } + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 32))) { + result[0] += -1022.6641412510904; + } else { + if (LIKELY(false || (data[7].qvalue <= 200))) { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += 256.4779043789177; + } else { + if (LIKELY(false || (data[1].qvalue <= 126))) { + if (LIKELY(false || (data[4].qvalue <= 68))) { + result[0] += -87.1798391962891; + } else { + result[0] += 43.00407443678074; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 62))) { + result[0] += -130.4952039313895; + } else { + result[0] += -240.59828244124444; + } + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 164))) { + result[0] += -424.70860218767706; + } else { + result[0] += -596.5247694936194; + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 260))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + if (UNLIKELY(false || (data[8].qvalue <= 2))) { + result[0] += 174.72777599410847; + } else { + result[0] += 11.536037972850965; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 6))) { + if (LIKELY(false || (data[9].qvalue <= 96))) { + if (LIKELY(false || (data[7].qvalue <= 72))) { + if (LIKELY(false || (data[0].qvalue <= 132))) { + result[0] += -134.39734952576543; + } else { + result[0] += -367.3809402303026; + } + } else { + result[0] += 85.4518440435695; + } + } else { + result[0] += 30.478639892590373; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 124))) { + if (UNLIKELY(false || (data[0].qvalue <= 68))) { + result[0] += -50.893812931197466; + } else { + if (LIKELY(false || (data[2].qvalue <= 200))) { + result[0] += -4.69601116410534; + } else { + result[0] += -265.1217064966412; + } + } + } else { + result[0] += -104.769463503831; + } + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 64))) { + if (UNLIKELY(false || (data[9].qvalue <= 36))) { + result[0] += 428.87545627753326; + } else { + if (UNLIKELY(false || (data[9].qvalue <= 38))) { + if (UNLIKELY(false || (data[0].qvalue <= 442))) { + result[0] += -1773.848510347332; + } else { + result[0] += 55.659715716371515; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += 359.22617617244686; + } else { + if (UNLIKELY(false || (data[8].qvalue <= 20))) { + result[0] += -44.87315510911888; + } else { + result[0] += 74.38569188031644; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 382))) { + if (UNLIKELY(false || (data[2].qvalue <= 140))) { + if (UNLIKELY(false || (data[3].qvalue <= 12))) { + if (UNLIKELY(false || (data[10].qvalue <= 40))) { + result[0] += -1410.981056764837; + } else { + result[0] += -637.2921393197482; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 56))) { + result[0] += -70.36055469147344; + } else { + result[0] += 175.7175261222059; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 146))) { + result[0] += -613.9698554996957; + } else { + if (LIKELY(false || (data[8].qvalue <= 154))) { + result[0] += -92.27993947562106; + } else { + result[0] += -383.51208633267225; + } + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 88))) { + if (UNLIKELY(false || (data[7].qvalue <= 80))) { + if (UNLIKELY(false || (data[2].qvalue <= 64))) { + result[0] += -11.98521625723796; + } else { + result[0] += -714.7673310356126; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 96))) { + result[0] += 284.6663603250349; + } else { + result[0] += -61.60569809902938; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 40))) { + if (UNLIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -1260.7217681991474; + } else { + result[0] += 135.29439352456583; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 114))) { + result[0] += 204.2658499096425; + } else { + result[0] += 26.57998407789056; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 278))) { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + if (LIKELY(false || (data[1].qvalue <= 10))) { + result[0] += 88.29835564376643; + } else { + result[0] += 433.48530065382903; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 158))) { + if (UNLIKELY(false || (data[4].qvalue <= 6))) { + if (UNLIKELY(false || (data[9].qvalue <= 134))) { + result[0] += 355.11754045683864; + } else { + result[0] += 50.689614734965716; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 10))) { + if (UNLIKELY(false || (data[9].qvalue <= 142))) { + result[0] += -67.45276443587568; + } else { + result[0] += -258.89458735449244; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 150))) { + result[0] += -21.308012427711205; + } else { + result[0] += 244.9050850901425; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 166))) { + result[0] += -159.91554699358346; + } else { + result[0] += -467.3807915108788; + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 180))) { + if (LIKELY(false || (data[0].qvalue <= 460))) { + if (LIKELY(false || (data[2].qvalue <= 214))) { + if (UNLIKELY(false || (data[9].qvalue <= 2))) { + if (UNLIKELY(false || (data[3].qvalue <= 98))) { + result[0] += -888.854216500035; + } else { + result[0] += -60.23569984287638; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 10))) { + result[0] += -107.7332013111863; + } else { + result[0] += 44.24648581216298; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 32))) { + if (LIKELY(false || (data[0].qvalue <= 446))) { + result[0] += -226.15311381622098; + } else { + result[0] += 710.1847247221976; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -521.4681372500785; + } else { + result[0] += -121.81009418188125; + } + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 156))) { + if (LIKELY(false || (data[4].qvalue <= 108))) { + if (UNLIKELY(false || (data[9].qvalue <= 36))) { + result[0] += 578.8864769214038; + } else { + result[0] += -65.43432145431503; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 470))) { + result[0] += -1140.9993698987162; + } else { + result[0] += -277.1047243415079; + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 124))) { + if (LIKELY(false || (data[2].qvalue <= 218))) { + result[0] += 625.8667118148287; + } else { + result[0] += 133.56293016701576; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 464))) { + result[0] += -257.6768725464037; + } else { + result[0] += 235.27089761951143; + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (UNLIKELY(false || (data[4].qvalue <= 88))) { + if (LIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -510.7544463506701; + } else { + result[0] += 309.52861873156445; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 186))) { + result[0] += -475.554284048819; + } else { + result[0] += -931.2955241523669; + } + } + } else { + result[0] += 204.12262051329452; + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 148))) { + if (LIKELY(false || (data[1].qvalue <= 60))) { + if (UNLIKELY(false || (data[0].qvalue <= 4))) { + result[0] += -105.41033083691937; + } else { + if (LIKELY(false || (data[7].qvalue <= 118))) { + result[0] += -9.957538169716495; + } else { + result[0] += -116.44279176689716; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 80))) { + if (LIKELY(false || (data[7].qvalue <= 138))) { + result[0] += -36.47666931987333; + } else { + result[0] += -181.90346126434108; + } + } else { + result[0] += -92.3026154859271; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 192))) { + if (LIKELY(false || (data[0].qvalue <= 380))) { + if (UNLIKELY(false || (data[9].qvalue <= 54))) { + if (UNLIKELY(false || (data[1].qvalue <= 34))) { + result[0] += 635.4266731901135; + } else { + if (UNLIKELY(false || (data[1].qvalue <= 56))) { + result[0] += -956.053733984008; + } else { + result[0] += -104.63367772570996; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 132))) { + if (UNLIKELY(false || (data[3].qvalue <= 34))) { + result[0] += -20.173896617001866; + } else { + result[0] += 65.25040747317426; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 136))) { + result[0] += -570.0499875540565; + } else { + result[0] += -4.395243842020363; + } + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 96))) { + if (LIKELY(false || (data[2].qvalue <= 150))) { + if (UNLIKELY(false || (data[7].qvalue <= 56))) { + result[0] += 82.3347657422682; + } else { + result[0] += -151.85939690566192; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 202))) { + result[0] += 306.7932008675081; + } else { + result[0] += -193.98901427646175; + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 30))) { + if (UNLIKELY(false || (data[10].qvalue <= 80))) { + result[0] += -101.31894339788478; + } else { + result[0] += 57.66783954209549; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 160))) { + result[0] += 304.3481193163534; + } else { + result[0] += 101.37427473767308; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 438))) { + if (LIKELY(false || (data[1].qvalue <= 98))) { + if (LIKELY(false || (data[3].qvalue <= 170))) { + result[0] += -173.79619958480487; + } else { + result[0] += -611.7431741530887; + } + } else { + result[0] += -479.5994428796921; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 174))) { + if (LIKELY(false || (data[0].qvalue <= 452))) { + if (LIKELY(false || (data[2].qvalue <= 194))) { + result[0] += 205.09978107028988; + } else { + result[0] += -552.4708829680587; + } + } else { + result[0] += 465.99445615301477; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 466))) { + if (UNLIKELY(false || (data[6].qvalue <= 180))) { + result[0] += -38.14040161366896; + } else { + result[0] += -654.1612824926979; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 164))) { + result[0] += 89.03239010969071; + } else { + result[0] += -334.95950567599243; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 324))) { + if (UNLIKELY(false || (data[6].qvalue <= 46))) { + if (LIKELY(false || (data[3].qvalue <= 70))) { + if (LIKELY(false || (data[3].qvalue <= 66))) { + if (LIKELY(false || (data[3].qvalue <= 34))) { + if (LIKELY(false || (data[2].qvalue <= 122))) { + result[0] += -18.433723366725584; + } else { + result[0] += -472.18904133491907; + } + } else { + result[0] += 41.144657963793065; + } + } else { + if (LIKELY(false || (data[10].qvalue <= 126))) { + if (UNLIKELY(false || (data[2].qvalue <= 70))) { + result[0] += -791.0825554146527; + } else { + result[0] += -77.87558566467489; + } + } else { + result[0] += -1273.8300701538087; + } + } + } else { + result[0] += 162.91309292890534; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + result[0] += -374.78034701138165; + } else { + result[0] += -30.23314802748434; + } + } + } else { + if (LIKELY(false || (data[10].qvalue <= 116))) { + if (LIKELY(false || (data[1].qvalue <= 152))) { + if (UNLIKELY(false || (data[2].qvalue <= 8))) { + if (LIKELY(false || (data[0].qvalue <= 458))) { + if (LIKELY(false || (data[6].qvalue <= 82))) { + result[0] += 19.475576346353392; + } else { + result[0] += -352.9733362293148; + } + } else { + result[0] += 349.1018486741264; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 28))) { + if (LIKELY(false || (data[0].qvalue <= 416))) { + result[0] += 212.94808346197698; + } else { + result[0] += -374.8210809697834; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 46))) { + result[0] += -34.43423741643343; + } else { + result[0] += 75.34901971114162; + } + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += 312.18232536892253; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 454))) { + result[0] += -380.2729414924723; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 74))) { + result[0] += 384.2278192657615; + } else { + result[0] += -48.08512286278479; + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 448))) { + if (LIKELY(false || (data[6].qvalue <= 124))) { + if (UNLIKELY(false || (data[2].qvalue <= 148))) { + result[0] += -339.175501983996; + } else { + result[0] += 73.82088972486432; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 122))) { + if (UNLIKELY(false || (data[3].qvalue <= 114))) { + result[0] += -690.6636434137862; + } else { + result[0] += -233.4604631893612; + } + } else { + result[0] += 61.13592985094737; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 176))) { + if (LIKELY(false || (data[2].qvalue <= 216))) { + if (LIKELY(false || (data[0].qvalue <= 466))) { + result[0] += 336.27393994775974; + } else { + result[0] += -1244.2348096296523; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -288.7929275242086; + } else { + result[0] += 303.1087488248426; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 470))) { + result[0] += -522.5254243038571; + } else { + result[0] += 147.10931954259047; + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 150))) { + if (UNLIKELY(false || (data[0].qvalue <= 2))) { + result[0] += -114.0935731263952; + } else { + if (LIKELY(false || (data[1].qvalue <= 102))) { + if (LIKELY(false || (data[4].qvalue <= 36))) { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (LIKELY(false || (data[5].qvalue <= 42))) { + result[0] += -18.695361121395486; + } else { + result[0] += 10.588982002368144; + } + } else { + result[0] += -172.85563078964458; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 30))) { + if (LIKELY(false || (data[10].qvalue <= 22))) { + result[0] += -51.938265058110325; + } else { + result[0] += -246.80507747694102; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 94))) { + result[0] += -27.959993628946236; + } else { + result[0] += 73.25664987336096; + } + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 154))) { + if (UNLIKELY(false || (data[8].qvalue <= 66))) { + if (UNLIKELY(false || (data[9].qvalue <= 34))) { + result[0] += -101.97494811248283; + } else { + result[0] += 17.881559562839353; + } + } else { + result[0] += -100.47614812809579; + } + } else { + result[0] += -213.33125633625428; + } + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + if (LIKELY(false || (data[1].qvalue <= 10))) { + if (UNLIKELY(false || (data[2].qvalue <= 24))) { + if (LIKELY(false || (data[0].qvalue <= 222))) { + result[0] += 434.3221282795032; + } else { + result[0] += 823.0770567103796; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 258))) { + if (LIKELY(false || (data[2].qvalue <= 128))) { + result[0] += 87.83445346165345; + } else { + result[0] += -212.4695657744424; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 128))) { + result[0] += 464.3977186968871; + } else { + result[0] += 163.9938488166673; + } + } + } + } else { + result[0] += 637.6504024717447; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 154))) { + if (UNLIKELY(false || (data[8].qvalue <= 0))) { + if (LIKELY(false || (data[0].qvalue <= 406))) { + if (LIKELY(false || (data[9].qvalue <= 90))) { + result[0] += 282.83342280184934; + } else { + result[0] += 120.66910156313747; + } + } else { + result[0] += 1028.390014143319; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + if (LIKELY(false || (data[0].qvalue <= 464))) { + result[0] += -292.64750935522983; + } else { + result[0] += 696.0738841280834; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 8))) { + result[0] += -98.38508109993742; + } else { + result[0] += 20.07618047102507; + } + } + } + } else { + if (LIKELY(false || (data[3].qvalue <= 20))) { + if (LIKELY(false || (data[0].qvalue <= 384))) { + if (UNLIKELY(false || (data[1].qvalue <= 14))) { + result[0] += -153.79848410394547; + } else { + result[0] += -491.2192559340425; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 430))) { + result[0] += 170.48551507912254; + } else { + result[0] += 726.1771275054432; + } + } + } else { + result[0] += 582.2067601142297; + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 384))) { + if (LIKELY(false || (data[6].qvalue <= 144))) { + if (LIKELY(false || (data[4].qvalue <= 120))) { + if (LIKELY(false || (data[8].qvalue <= 138))) { + if (LIKELY(false || (data[3].qvalue <= 122))) { + if (LIKELY(false || (data[6].qvalue <= 78))) { + result[0] += 6.611296900045464; + } else { + result[0] += -146.7747758716442; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 106))) { + result[0] += 117.39498296773701; + } else { + result[0] += -52.139056254522906; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 64))) { + result[0] += -786.3744050601753; + } else { + result[0] += -75.75876514356197; + } + } + } else { + result[0] += -251.69019507498334; + } + } else { + result[0] += -159.81350735348502; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 10))) { + if (UNLIKELY(false || (data[0].qvalue <= 450))) { + if (LIKELY(false || (data[3].qvalue <= 168))) { + if (UNLIKELY(false || (data[8].qvalue <= 78))) { + result[0] += -655.1579692026768; + } else { + result[0] += -234.92840903417084; + } + } else { + result[0] += 129.69681018242764; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 174))) { + if (LIKELY(false || (data[7].qvalue <= 174))) { + if (UNLIKELY(false || (data[8].qvalue <= 54))) { + result[0] += -139.81070363809008; + } else { + result[0] += 236.49651798822757; + } + } else { + result[0] += 997.2638005655676; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 468))) { + result[0] += -440.5340347804863; + } else { + result[0] += 133.34746925104315; + } + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 96))) { + if (LIKELY(false || (data[3].qvalue <= 76))) { + if (UNLIKELY(false || (data[8].qvalue <= 80))) { + if (UNLIKELY(false || (data[9].qvalue <= 16))) { + result[0] += 953.3985019502752; + } else { + result[0] += 115.36154895354196; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 68))) { + result[0] += -75.90121598789128; + } else { + result[0] += 321.1318897275337; + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 78))) { + if (LIKELY(false || (data[0].qvalue <= 442))) { + result[0] += -896.7922993531249; + } else { + result[0] += -192.64263220387716; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 82))) { + result[0] += -157.71189744728704; + } else { + result[0] += 388.1757773943014; + } + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 128))) { + if (LIKELY(false || (data[0].qvalue <= 426))) { + if (LIKELY(false || (data[6].qvalue <= 140))) { + result[0] += 239.69457507298395; + } else { + result[0] += -339.18339092719594; + } + } else { + result[0] += 440.89438255994565; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 48))) { + if (LIKELY(false || (data[8].qvalue <= 28))) { + result[0] += 94.78432124431386; + } else { + result[0] += -585.5090672534587; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 118))) { + result[0] += 261.6165504519048; + } else { + result[0] += -0.9669883257810821; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 130))) { + if (UNLIKELY(false || (data[0].qvalue <= 6))) { + result[0] += -85.38452227119578; + } else { + if (LIKELY(false || (data[1].qvalue <= 60))) { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + result[0] += 71.39641555324053; + } else { + result[0] += -12.679337084314707; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 70))) { + if (UNLIKELY(false || (data[9].qvalue <= 46))) { + result[0] += -328.3515745861993; + } else { + result[0] += -24.65068942389365; + } + } else { + result[0] += -65.54687009952785; + } + } + } + } else { + if (LIKELY(false || (data[10].qvalue <= 122))) { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + if (LIKELY(false || (data[1].qvalue <= 10))) { + if (UNLIKELY(false || (data[2].qvalue <= 24))) { + result[0] += 448.8186025793332; + } else { + if (LIKELY(false || (data[0].qvalue <= 262))) { + result[0] += 28.507570304853807; + } else { + result[0] += 334.06403364310586; + } + } + } else { + result[0] += 565.4514138078015; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 154))) { + if (LIKELY(false || (data[9].qvalue <= 148))) { + if (UNLIKELY(false || (data[1].qvalue <= 16))) { + result[0] += -96.69821678256282; + } else { + result[0] += 21.631660184261378; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 204))) { + result[0] += 354.2452862416222; + } else { + result[0] += -51.61557505903728; + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 12))) { + if (LIKELY(false || (data[0].qvalue <= 372))) { + result[0] += -328.0629299855443; + } else { + result[0] += 135.76618575654695; + } + } else { + result[0] += 497.32398174001605; + } + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 136))) { + if (LIKELY(false || (data[0].qvalue <= 416))) { + if (UNLIKELY(false || (data[2].qvalue <= 134))) { + if (UNLIKELY(false || (data[0].qvalue <= 354))) { + result[0] += -15.704885988027627; + } else { + result[0] += -432.2700366601563; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 398))) { + result[0] += -1249.1625247677366; + } else { + result[0] += -669.3611102779328; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 428))) { + result[0] += -58.35712345907448; + } else { + if (LIKELY(false || (data[0].qvalue <= 438))) { + result[0] += 338.03248915141415; + } else { + result[0] += 777.2208330972686; + } + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 84))) { + if (LIKELY(false || (data[0].qvalue <= 426))) { + if (LIKELY(false || (data[1].qvalue <= 154))) { + result[0] += 120.71513738942753; + } else { + result[0] += -308.17490477956534; + } + } else { + result[0] += 833.731541937934; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 438))) { + if (UNLIKELY(false || (data[9].qvalue <= 6))) { + result[0] += 149.7928136109008; + } else { + result[0] += -223.98734671939062; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 152))) { + result[0] += 365.23772234337326; + } else { + result[0] += -24.192037541431123; + } + } + } + } + } + } + if (LIKELY(false || (data[7].qvalue <= 192))) { + if (LIKELY(false || (data[0].qvalue <= 388))) { + if (LIKELY(false || (data[1].qvalue <= 124))) { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (LIKELY(false || (data[3].qvalue <= 122))) { + if (LIKELY(false || (data[8].qvalue <= 116))) { + result[0] += 4.536955058087513; + } else { + result[0] += -72.38105398500589; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + result[0] += -344.4937340104233; + } else { + result[0] += 54.04370832477616; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 352))) { + result[0] += -155.8470275480651; + } else { + result[0] += -517.2572856835848; + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 64))) { + if (LIKELY(false || (data[8].qvalue <= 64))) { + if (UNLIKELY(false || (data[3].qvalue <= 62))) { + result[0] += 140.37617439343134; + } else { + result[0] += -55.112467004567236; + } + } else { + result[0] += 774.980876755253; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 346))) { + if (UNLIKELY(false || (data[8].qvalue <= 38))) { + result[0] += -397.1965385921262; + } else { + result[0] += -92.74338066885616; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 36))) { + result[0] += 475.3460832912639; + } else { + result[0] += -489.2890878250337; + } + } + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 96))) { + if (UNLIKELY(false || (data[8].qvalue <= 10))) { + result[0] += 386.3622072624303; + } else { + if (LIKELY(false || (data[2].qvalue <= 150))) { + if (LIKELY(false || (data[0].qvalue <= 452))) { + result[0] += -138.2046386901946; + } else { + result[0] += 109.19811106405169; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 202))) { + result[0] += 279.484508613883; + } else { + result[0] += -160.50141487078906; + } + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 30))) { + if (UNLIKELY(false || (data[8].qvalue <= 48))) { + if (LIKELY(false || (data[2].qvalue <= 46))) { + result[0] += 96.50006530829478; + } else { + result[0] += -311.4323024052799; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 114))) { + result[0] += 221.35954689453885; + } else { + result[0] += -11.21222614010631; + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 12))) { + if (LIKELY(false || (data[0].qvalue <= 462))) { + result[0] += -250.44082389008545; + } else { + result[0] += 259.77584765088653; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 86))) { + result[0] += 361.4943477960914; + } else { + result[0] += 91.34722785277711; + } + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 436))) { + if (UNLIKELY(false || (data[0].qvalue <= 350))) { + result[0] += -160.4323829658149; + } else { + result[0] += -425.59656420877354; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 146))) { + result[0] += 180.15448233040198; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -433.82026787118116; + } else { + result[0] += -59.26399277285995; + } + } + } + } + if (LIKELY(false || (data[7].qvalue <= 178))) { + if (LIKELY(false || (data[0].qvalue <= 460))) { + if (UNLIKELY(false || (data[9].qvalue <= 2))) { + if (LIKELY(false || (data[7].qvalue <= 152))) { + if (LIKELY(false || (data[0].qvalue <= 442))) { + result[0] += -308.1244882642975; + } else { + if (LIKELY(false || (data[1].qvalue <= 154))) { + result[0] += 509.7187192299381; + } else { + result[0] += -455.2150339355469; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 428))) { + result[0] += -189.49508286587985; + } else { + result[0] += -760.2093864229564; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 212))) { + if (LIKELY(false || (data[7].qvalue <= 166))) { + if (LIKELY(false || (data[7].qvalue <= 164))) { + result[0] += 3.4436391818207746; + } else { + result[0] += -682.0310140822548; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 388))) { + result[0] += 92.34103234198413; + } else { + result[0] += 380.69157714277566; + } + } + } else { + result[0] += -165.7227313266843; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 156))) { + if (LIKELY(false || (data[4].qvalue <= 108))) { + if (UNLIKELY(false || (data[9].qvalue <= 36))) { + result[0] += 549.4816677105838; + } else { + if (LIKELY(false || (data[1].qvalue <= 58))) { + result[0] += 194.29034273702734; + } else { + result[0] += -515.5656251305937; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 472))) { + if (UNLIKELY(false || (data[9].qvalue <= 6))) { + result[0] += 310.8152990014213; + } else { + result[0] += -1141.971330367939; + } + } else { + result[0] += -60.374245828492555; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 176))) { + result[0] += 351.2745651968375; + } else { + result[0] += 86.38384679612086; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 416))) { + if (UNLIKELY(false || (data[2].qvalue <= 162))) { + if (LIKELY(false || (data[2].qvalue <= 142))) { + result[0] += -197.5086799826019; + } else { + if (LIKELY(false || (data[0].qvalue <= 332))) { + result[0] += -304.0321579015652; + } else { + result[0] += -814.8576146638849; + } + } + } else { + result[0] += -77.31156609799358; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 114))) { + if (LIKELY(false || (data[7].qvalue <= 198))) { + result[0] += 364.03422368739893; + } else { + if (LIKELY(false || (data[0].qvalue <= 448))) { + result[0] += -262.12017291355977; + } else { + result[0] += 355.1467205041031; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (LIKELY(false || (data[6].qvalue <= 180))) { + if (LIKELY(false || (data[2].qvalue <= 222))) { + result[0] += 34.07266539417179; + } else { + result[0] += -461.43650166079533; + } + } else { + result[0] += -392.65138527512556; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 188))) { + result[0] += 424.2608283476747; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -401.74970612209404; + } else { + result[0] += 149.51617217256646; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 318))) { + if (UNLIKELY(false || (data[7].qvalue <= 16))) { + if (LIKELY(false || (data[5].qvalue <= 46))) { + result[0] += 6.274580412893512; + } else { + result[0] += 170.65850290785482; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 28))) { + if (UNLIKELY(false || (data[7].qvalue <= 28))) { + result[0] += -321.26713386171923; + } else { + if (LIKELY(false || (data[10].qvalue <= 24))) { + if (UNLIKELY(false || (data[3].qvalue <= 0))) { + result[0] += -883.3009822319002; + } else { + result[0] += -30.131313419296436; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 44))) { + result[0] += 145.86018225187783; + } else { + result[0] += -533.3653617127321; + } + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 46))) { + if (LIKELY(false || (data[8].qvalue <= 114))) { + if (UNLIKELY(false || (data[3].qvalue <= 32))) { + result[0] += -64.30753875016195; + } else { + result[0] += 70.480411627325; + } + } else { + if (LIKELY(false || (data[8].qvalue <= 132))) { + result[0] += -282.2392212961099; + } else { + result[0] += 67.65616622116742; + } + } + } else { + result[0] += -24.453451185191113; + } + } + } + } else { + if (LIKELY(false || (data[10].qvalue <= 116))) { + if (UNLIKELY(false || (data[9].qvalue <= 10))) { + if (LIKELY(false || (data[0].qvalue <= 466))) { + if (UNLIKELY(false || (data[8].qvalue <= 26))) { + result[0] += -607.6977777871718; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 442))) { + result[0] += -273.64198324787975; + } else { + result[0] += 45.47924148212965; + } + } + } else { + result[0] += 215.11954437098967; + } + } else { + result[0] += 45.61441585551685; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 440))) { + if (LIKELY(false || (data[6].qvalue <= 124))) { + if (LIKELY(false || (data[0].qvalue <= 414))) { + if (UNLIKELY(false || (data[6].qvalue <= 42))) { + result[0] += -454.72131809174454; + } else { + result[0] += 0.13988354882495432; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 56))) { + result[0] += 38.582001327207514; + } else { + result[0] += 525.027278044562; + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 20))) { + if (UNLIKELY(false || (data[6].qvalue <= 132))) { + result[0] += -698.2304850150304; + } else { + result[0] += -55.05691788452429; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 130))) { + result[0] += -678.96875763753; + } else { + result[0] += -223.46103248730483; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 170))) { + if (LIKELY(false || (data[4].qvalue <= 122))) { + if (LIKELY(false || (data[9].qvalue <= 16))) { + result[0] += -90.61321602369662; + } else { + result[0] += 261.0377505016003; + } + } else { + result[0] += 460.8651737454858; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -518.3029692442528; + } else { + if (UNLIKELY(false || (data[4].qvalue <= 102))) { + result[0] += 251.99443410934916; + } else { + result[0] += -168.5258406883114; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 88))) { + if (UNLIKELY(false || (data[0].qvalue <= 2))) { + result[0] += -93.52752297088361; + } else { + if (LIKELY(false || (data[1].qvalue <= 92))) { + if (UNLIKELY(false || (data[1].qvalue <= 38))) { + if (UNLIKELY(false || (data[0].qvalue <= 22))) { + result[0] += -28.36906776557731; + } else { + if (LIKELY(false || (data[4].qvalue <= 94))) { + result[0] += -3.2423893406338657; + } else { + result[0] += 138.541654434318; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 22))) { + result[0] += -46.28911386105568; + } else { + if (LIKELY(false || (data[3].qvalue <= 150))) { + result[0] += -22.298009717414818; + } else { + result[0] += -81.59335058549901; + } + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 88))) { + if (LIKELY(false || (data[4].qvalue <= 132))) { + result[0] += -66.17939589753632; + } else { + result[0] += -205.33954408242673; + } + } else { + result[0] += 8.493295470465377; + } + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 2))) { + if (LIKELY(false || (data[5].qvalue <= 6))) { + if (LIKELY(false || (data[0].qvalue <= 264))) { + if (UNLIKELY(false || (data[5].qvalue <= 0))) { + if (LIKELY(false || (data[0].qvalue <= 224))) { + result[0] += 326.1908307836102; + } else { + result[0] += 712.5672110896917; + } + } else { + result[0] += 165.97117149989344; + } + } else { + result[0] += 512.7394095968342; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 290))) { + result[0] += -88.61952314673287; + } else { + result[0] += 257.63671136127255; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 8))) { + if (LIKELY(false || (data[0].qvalue <= 464))) { + if (LIKELY(false || (data[3].qvalue <= 166))) { + if (LIKELY(false || (data[4].qvalue <= 82))) { + result[0] += -64.2972163819839; + } else { + result[0] += -678.5598075508324; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -743.1003478462518; + } else { + result[0] += 22.380609435146425; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (LIKELY(false || (data[1].qvalue <= 2))) { + result[0] += 668.8097667814556; + } else { + result[0] += 172.2438155603644; + } + } else { + result[0] += -103.48953914040013; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 14))) { + if (LIKELY(false || (data[9].qvalue <= 150))) { + if (UNLIKELY(false || (data[9].qvalue <= 112))) { + result[0] += 210.79347178390668; + } else { + result[0] += -66.34475012824036; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 8))) { + result[0] += 79.44162948474975; + } else { + result[0] += 423.58682172057206; + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 154))) { + if (UNLIKELY(false || (data[1].qvalue <= 34))) { + result[0] += 58.63080216913425; + } else { + result[0] += 0.56660428680394; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 388))) { + result[0] += -354.99641077814067; + } else { + result[0] += 163.82541020213822; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 428))) { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[0].qvalue <= 364))) { + result[0] += -4.43201919585627; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 2))) { + if (LIKELY(false || (data[3].qvalue <= 148))) { + if (LIKELY(false || (data[4].qvalue <= 96))) { + result[0] += -461.34171468334483; + } else { + result[0] += -1974.1613360699155; + } + } else { + result[0] += 464.12272874367665; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 16))) { + if (UNLIKELY(false || (data[2].qvalue <= 116))) { + result[0] += -2425.075299189815; + } else { + result[0] += -330.4655251495208; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 72))) { + result[0] += 166.19696442999253; + } else { + result[0] += 9.303779051447991; + } + } + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 42))) { + result[0] += -481.590488188672; + } else { + if (UNLIKELY(false || (data[10].qvalue <= 84))) { + if (UNLIKELY(false || (data[2].qvalue <= 154))) { + result[0] += -201.8241770516161; + } else { + if (LIKELY(false || (data[0].qvalue <= 400))) { + result[0] += 51.47643177844849; + } else { + result[0] += 557.8587391367337; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 356))) { + result[0] += -68.84035678322192; + } else { + result[0] += -259.4256502962815; + } + } + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 104))) { + if (LIKELY(false || (data[5].qvalue <= 100))) { + if (UNLIKELY(false || (data[0].qvalue <= 454))) { + if (UNLIKELY(false || (data[4].qvalue <= 78))) { + result[0] += 156.73836309324633; + } else { + if (LIKELY(false || (data[4].qvalue <= 138))) { + result[0] += -607.0488890568329; + } else { + result[0] += 9.03668551193256; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 100))) { + if (UNLIKELY(false || (data[6].qvalue <= 40))) { + result[0] += -1017.2723550166346; + } else { + result[0] += 91.3800484259382; + } + } else { + result[0] += -942.7966219121105; + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 26))) { + if (UNLIKELY(false || (data[0].qvalue <= 464))) { + if (UNLIKELY(false || (data[4].qvalue <= 114))) { + result[0] += 346.1853239128133; + } else { + result[0] += -870.2639669615536; + } + } else { + result[0] += 136.37634000604268; + } + } else { + result[0] += 483.8076223319302; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 104))) { + if (UNLIKELY(false || (data[8].qvalue <= 76))) { + result[0] += 632.1932202439316; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 114))) { + result[0] += 859.7961658998064; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 110))) { + result[0] += 6.445434627545658; + } else { + result[0] += 232.20573728889286; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 450))) { + if (UNLIKELY(false || (data[10].qvalue <= 62))) { + result[0] += -766.6301885459153; + } else { + result[0] += -66.31539816252801; + } + } else { + result[0] += 73.09136207866754; + } + } + } + } + if (LIKELY(false || (data[7].qvalue <= 192))) { + if (LIKELY(false || (data[10].qvalue <= 122))) { + if (LIKELY(false || (data[3].qvalue <= 102))) { + if (LIKELY(false || (data[7].qvalue <= 44))) { + if (UNLIKELY(false || (data[6].qvalue <= 24))) { + if (LIKELY(false || (data[7].qvalue <= 8))) { + result[0] += 28.34884333239003; + } else { + result[0] += -46.41782030735656; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 73.71689530827445; + } else { + result[0] += 1.4571574901978375; + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 68))) { + if (UNLIKELY(false || (data[3].qvalue <= 2))) { + result[0] += -319.74328138081097; + } else { + result[0] += 28.72734330489961; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 74))) { + result[0] += -392.30286128897285; + } else { + result[0] += -56.63552344488173; + } + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + if (LIKELY(false || (data[8].qvalue <= 148))) { + if (UNLIKELY(false || (data[3].qvalue <= 134))) { + result[0] += -61.214933692783916; + } else { + result[0] += -207.6113943042064; + } + } else { + result[0] += -632.4056164963666; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 36))) { + if (LIKELY(false || (data[8].qvalue <= 28))) { + result[0] += 0.776759507119416; + } else { + result[0] += -563.4328564341248; + } + } else { + if (LIKELY(false || (data[8].qvalue <= 140))) { + result[0] += 80.66532309064849; + } else { + result[0] += -37.64303198703362; + } + } + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 128))) { + if (LIKELY(false || (data[9].qvalue <= 80))) { + if (UNLIKELY(false || (data[1].qvalue <= 142))) { + result[0] += 3.0907605764638824; + } else { + result[0] += -191.9470886917915; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 42))) { + result[0] += -535.3539427021846; + } else { + result[0] += -747.1267209093315; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 84))) { + if (UNLIKELY(false || (data[4].qvalue <= 48))) { + if (UNLIKELY(false || (data[1].qvalue <= 48))) { + result[0] += 39.65250739364507; + } else { + result[0] += -83.33176284276131; + } + } else { + if (LIKELY(false || (data[10].qvalue <= 144))) { + result[0] += 137.86981971555903; + } else { + result[0] += -47.78103683170895; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 6))) { + if (LIKELY(false || (data[8].qvalue <= 108))) { + result[0] += 265.07465212054825; + } else { + result[0] += -112.5511941043707; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 130))) { + result[0] += -185.10561993544567; + } else { + result[0] += -55.42540507337779; + } + } + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 200))) { + if (LIKELY(false || (data[1].qvalue <= 164))) { + result[0] += -71.05204569728328; + } else { + if (LIKELY(false || (data[3].qvalue <= 174))) { + result[0] += -229.96092256575585; + } else { + result[0] += -663.863689584278; + } + } + } else { + result[0] += -404.1617066176453; + } + } + if (LIKELY(false || (data[0].qvalue <= 434))) { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[0].qvalue <= 374))) { + if (LIKELY(false || (data[4].qvalue <= 120))) { + if (LIKELY(false || (data[2].qvalue <= 176))) { + if (LIKELY(false || (data[5].qvalue <= 80))) { + result[0] += -4.033785114047066; + } else { + result[0] += 51.688746674364644; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 42))) { + result[0] += -308.48199870168713; + } else { + result[0] += -36.130910480232906; + } + } + } else { + result[0] += -195.1197382760659; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 2))) { + if (LIKELY(false || (data[4].qvalue <= 110))) { + if (LIKELY(false || (data[4].qvalue <= 96))) { + result[0] += -408.3567730759215; + } else { + result[0] += -1707.6448987068966; + } + } else { + result[0] += 485.8821080747238; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 16))) { + if (UNLIKELY(false || (data[2].qvalue <= 116))) { + result[0] += -2096.4662113083464; + } else { + result[0] += -276.14738618844734; + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 68))) { + result[0] += -8.171064906722554; + } else { + result[0] += 155.11413889664198; + } + } + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 46))) { + result[0] += -426.0821034776104; + } else { + if (UNLIKELY(false || (data[10].qvalue <= 84))) { + if (UNLIKELY(false || (data[2].qvalue <= 154))) { + if (LIKELY(false || (data[0].qvalue <= 420))) { + result[0] += -251.87299899417505; + } else { + result[0] += 171.7813777604329; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 188))) { + result[0] += 266.03680241951855; + } else { + result[0] += -230.94110204440864; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 98))) { + if (UNLIKELY(false || (data[9].qvalue <= 14))) { + result[0] += -51.4712788511078; + } else { + result[0] += -349.1585791389674; + } + } else { + result[0] += -87.62955894052737; + } + } + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 14))) { + result[0] += -962.914071451823; + } else { + if (LIKELY(false || (data[6].qvalue <= 180))) { + if (UNLIKELY(false || (data[2].qvalue <= 104))) { + if (UNLIKELY(false || (data[10].qvalue <= 4))) { + result[0] += 410.01446934535943; + } else { + if (UNLIKELY(false || (data[1].qvalue <= 144))) { + result[0] += -158.79533984060308; + } else { + result[0] += 54.19442054392661; + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 64))) { + result[0] += 800.9582024560236; + } else { + if (LIKELY(false || (data[2].qvalue <= 214))) { + result[0] += 152.70184903575273; + } else { + result[0] += -39.258349058638146; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 472))) { + if (LIKELY(false || (data[10].qvalue <= 106))) { + if (UNLIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -385.9941662617043; + } else { + result[0] += 109.98038694597618; + } + } else { + result[0] += -519.7043617216136; + } + } else { + result[0] += 312.6413190598439; + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 450))) { + if (LIKELY(false || (data[6].qvalue <= 146))) { + if (LIKELY(false || (data[7].qvalue <= 198))) { + if (LIKELY(false || (data[0].qvalue <= 366))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + if (LIKELY(false || (data[1].qvalue <= 74))) { + result[0] += 137.297555227154; + } else { + result[0] += -254.9833262356102; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 8))) { + result[0] += -123.87983644564534; + } else { + result[0] += -2.4145863679975013; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 6))) { + if (LIKELY(false || (data[6].qvalue <= 82))) { + result[0] += 51.07636765965205; + } else { + result[0] += -390.1200629057833; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 28))) { + result[0] += 215.48035596295546; + } else { + result[0] += 39.12939193887718; + } + } + } + } else { + result[0] += -235.5281315434429; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 28))) { + result[0] += -534.086589766028; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 156))) { + if (LIKELY(false || (data[7].qvalue <= 166))) { + result[0] += -253.52810060662947; + } else { + if (UNLIKELY(false || (data[6].qvalue <= 164))) { + result[0] += 451.54660129512826; + } else { + result[0] += -6.922248765373611; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 168))) { + if (LIKELY(false || (data[0].qvalue <= 422))) { + result[0] += -87.41127255128306; + } else { + result[0] += 161.7062666527756; + } + } else { + if (LIKELY(false || (data[8].qvalue <= 96))) { + result[0] += -362.4373441748169; + } else { + result[0] += -47.688779690032106; + } + } + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 180))) { + if (UNLIKELY(false || (data[6].qvalue <= 70))) { + if (LIKELY(false || (data[3].qvalue <= 118))) { + result[0] += -109.71948701870082; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -3168.276688179348; + } else { + result[0] += -655.8877042161603; + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 108))) { + if (UNLIKELY(false || (data[7].qvalue <= 148))) { + if (LIKELY(false || (data[6].qvalue <= 138))) { + result[0] += 211.55392830640332; + } else { + result[0] += 544.2392075566188; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 460))) { + result[0] += -137.766397826111; + } else { + result[0] += 227.29084096946204; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 114))) { + if (UNLIKELY(false || (data[0].qvalue <= 470))) { + result[0] += -1219.702639935603; + } else { + result[0] += 19.073660451518418; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 168))) { + result[0] += 180.8219311362737; + } else { + result[0] += -23.62961539090255; + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (UNLIKELY(false || (data[4].qvalue <= 88))) { + result[0] += -24.096509384288314; + } else { + result[0] += -474.0824089535872; + } + } else { + result[0] += 151.94918465665242; + } + } + } + if (LIKELY(false || (data[8].qvalue <= 156))) { + if (LIKELY(false || (data[0].qvalue <= 450))) { + if (LIKELY(false || (data[6].qvalue <= 146))) { + if (LIKELY(false || (data[0].qvalue <= 344))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + if (LIKELY(false || (data[0].qvalue <= 222))) { + result[0] += 39.972789080579226; + } else { + result[0] += 214.53042647495874; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 8))) { + result[0] += -103.36098764508137; + } else { + result[0] += -3.5862855800631728; + } + } + } else { + if (LIKELY(false || (data[3].qvalue <= 144))) { + if (LIKELY(false || (data[5].qvalue <= 92))) { + result[0] += 33.48501259384832; + } else { + result[0] += -526.1972342777782; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 188))) { + result[0] += 291.7875686572814; + } else { + result[0] += -154.8400445536322; + } + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 50))) { + if (UNLIKELY(false || (data[6].qvalue <= 152))) { + if (LIKELY(false || (data[0].qvalue <= 436))) { + result[0] += -209.00331042067378; + } else { + result[0] += 476.30810024115374; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 284))) { + result[0] += -174.50986784466022; + } else { + result[0] += -612.8591006144763; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 154))) { + if (LIKELY(false || (data[6].qvalue <= 160))) { + result[0] += -17.361861237499827; + } else { + result[0] += -227.84455664039766; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 170))) { + result[0] += 141.6717472259187; + } else { + result[0] += -152.85677116643248; + } + } + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 16))) { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -2290.8527835669884; + } else { + result[0] += -90.00394883304172; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 0))) { + if (UNLIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -670.6849931746262; + } else { + result[0] += -81.08626512407443; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 124))) { + if (UNLIKELY(false || (data[10].qvalue <= 44))) { + result[0] += 252.92553318096964; + } else { + result[0] += -237.02792802064724; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 26))) { + result[0] += -131.25931683115354; + } else { + result[0] += 155.92595301503033; + } + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 416))) { + if (LIKELY(false || (data[0].qvalue <= 350))) { + result[0] += -135.57120230355562; + } else { + result[0] += -444.05965584753494; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 130))) { + if (UNLIKELY(false || (data[0].qvalue <= 454))) { + result[0] += -617.9283577463137; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 168))) { + result[0] += 379.3453933956734; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 468))) { + result[0] += -555.1703256848654; + } else { + result[0] += 183.81435330137998; + } + } + } + } else { + result[0] += 287.9785027492729; + } + } + } + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (LIKELY(false || (data[0].qvalue <= 462))) { + if (LIKELY(false || (data[1].qvalue <= 160))) { + if (LIKELY(false || (data[6].qvalue <= 174))) { + if (LIKELY(false || (data[7].qvalue <= 166))) { + if (LIKELY(false || (data[7].qvalue <= 164))) { + result[0] += 1.9227744347010873; + } else { + result[0] += -372.9806790342511; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 162))) { + result[0] += 38.027655688643975; + } else { + result[0] += 330.84712805066437; + } + } + } else { + result[0] += -254.51641015239204; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 430))) { + result[0] += -29.47274599196618; + } else { + result[0] += -712.7969392811483; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 156))) { + if (LIKELY(false || (data[4].qvalue <= 108))) { + if (UNLIKELY(false || (data[9].qvalue <= 36))) { + result[0] += 557.7390203473873; + } else { + if (UNLIKELY(false || (data[1].qvalue <= 58))) { + result[0] += 232.35239699183904; + } else { + result[0] += -374.7341949643208; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 470))) { + if (UNLIKELY(false || (data[9].qvalue <= 6))) { + result[0] += 438.92836082611086; + } else { + result[0] += -1439.2171157594041; + } + } else { + result[0] += -146.03654903994473; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 176))) { + if (LIKELY(false || (data[4].qvalue <= 130))) { + if (LIKELY(false || (data[5].qvalue <= 108))) { + result[0] += 321.2116732677101; + } else { + result[0] += 754.9708259292751; + } + } else { + result[0] += -22.672436608018547; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 70))) { + result[0] += 795.3453671875; + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + result[0] += -213.941419408782; + } else { + result[0] += 282.16809176472924; + } + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (UNLIKELY(false || (data[5].qvalue <= 32))) { + result[0] += -1600.845136858259; + } else { + if (LIKELY(false || (data[6].qvalue <= 180))) { + if (LIKELY(false || (data[0].qvalue <= 440))) { + if (UNLIKELY(false || (data[5].qvalue <= 58))) { + result[0] += -521.5318192486991; + } else { + result[0] += -96.30084920460428; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 218))) { + result[0] += 166.08593687552778; + } else { + result[0] += -341.6197044372843; + } + } + } else { + result[0] += -270.16752261143085; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 188))) { + result[0] += 379.4922879662453; + } else { + if (LIKELY(false || (data[1].qvalue <= 164))) { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + if (LIKELY(false || (data[3].qvalue <= 178))) { + result[0] += 59.33023477478028; + } else { + result[0] += -944.8980152625645; + } + } else { + result[0] += 362.7230938296502; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -1894.5816613281252; + } else { + result[0] += -151.54009237179278; + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 466))) { + if (LIKELY(false || (data[6].qvalue <= 180))) { + if (LIKELY(false || (data[2].qvalue <= 218))) { + if (LIKELY(false || (data[0].qvalue <= 428))) { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[10].qvalue <= 146))) { + result[0] += 2.61150493746397; + } else { + result[0] += -294.8989575123201; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 154))) { + result[0] += -144.860709390818; + } else { + result[0] += 49.05217218783569; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 104))) { + if (LIKELY(false || (data[2].qvalue <= 102))) { + result[0] += -69.84846406106546; + } else { + result[0] += -1099.694205050492; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 64))) { + result[0] += 547.2763598289783; + } else { + result[0] += 121.02960542039312; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 430))) { + if (UNLIKELY(false || (data[1].qvalue <= 112))) { + result[0] += -299.9131036348163; + } else { + result[0] += 87.20580911412755; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 162))) { + if (LIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -434.7498970777533; + } else { + result[0] += 375.0325198630567; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 174))) { + result[0] += -761.131324712887; + } else { + result[0] += -387.49766429457327; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 392))) { + result[0] += -94.7425416912909; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 154))) { + result[0] += -190.41458346534543; + } else { + result[0] += -405.4827892391865; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 200))) { + if (UNLIKELY(false || (data[7].qvalue <= 78))) { + if (UNLIKELY(false || (data[2].qvalue <= 46))) { + result[0] += 373.04881527855287; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -1545.4975544084823; + } else { + result[0] += -255.72717774402136; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 98))) { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (LIKELY(false || (data[1].qvalue <= 116))) { + result[0] += 450.93153398469025; + } else { + result[0] += 1014.2956421685988; + } + } else { + result[0] += 1245.0624796875002; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 148))) { + if (UNLIKELY(false || (data[8].qvalue <= 34))) { + result[0] += -547.0456131590985; + } else { + result[0] += -9.966885090949628; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 104))) { + result[0] += 305.58289721685486; + } else { + result[0] += 51.01148885239249; + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + if (LIKELY(false || (data[1].qvalue <= 162))) { + result[0] += -921.1548865874474; + } else { + result[0] += -1870.7389783653846; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 162))) { + result[0] += 236.7134294836506; + } else { + result[0] += -285.6914099083137; + } + } + } + } + if (LIKELY(false || (data[7].qvalue <= 174))) { + if (UNLIKELY(false || (data[5].qvalue <= 14))) { + if (UNLIKELY(false || (data[4].qvalue <= 2))) { + if (LIKELY(false || (data[2].qvalue <= 58))) { + if (LIKELY(false || (data[2].qvalue <= 52))) { + result[0] += 138.46445954751613; + } else { + result[0] += 30.25739363045126; + } + } else { + result[0] += 308.1558521464535; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 40))) { + if (LIKELY(false || (data[10].qvalue <= 52))) { + if (LIKELY(false || (data[8].qvalue <= 12))) { + result[0] += -8.275799037633549; + } else { + result[0] += -133.54923320626136; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 6))) { + result[0] += 175.8079980773678; + } else { + result[0] += 60.614919146381446; + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 42))) { + result[0] += -434.16667085044185; + } else { + if (LIKELY(false || (data[4].qvalue <= 38))) { + result[0] += -41.16427694169654; + } else { + result[0] += -185.9750821120982; + } + } + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 22))) { + if (LIKELY(false || (data[3].qvalue <= 18))) { + if (LIKELY(false || (data[5].qvalue <= 20))) { + if (UNLIKELY(false || (data[4].qvalue <= 12))) { + result[0] += -122.25698184102133; + } else { + result[0] += 80.57705277421712; + } + } else { + result[0] += -153.60843419206896; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 22))) { + result[0] += 408.69803721206245; + } else { + if (UNLIKELY(false || (data[8].qvalue <= 90))) { + result[0] += 136.97672358425646; + } else { + result[0] += 270.9923573487174; + } + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 28))) { + if (LIKELY(false || (data[2].qvalue <= 122))) { + if (UNLIKELY(false || (data[3].qvalue <= 24))) { + result[0] += -159.08908713728795; + } else { + result[0] += -9.923411608506871; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 148))) { + result[0] += -521.1873971145586; + } else { + result[0] += 17.25752566881542; + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 2))) { + if (UNLIKELY(false || (data[3].qvalue <= 64))) { + result[0] += 301.7622074237134; + } else { + result[0] += 122.86657003996795; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 4))) { + result[0] += -254.85463978758668; + } else { + result[0] += 5.494987706460857; + } + } + } + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 32))) { + result[0] += -834.7163352102; + } else { + if (LIKELY(false || (data[7].qvalue <= 200))) { + if (UNLIKELY(false || (data[8].qvalue <= 6))) { + result[0] += 172.7220039048895; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += 161.15720525096583; + } else { + if (UNLIKELY(false || (data[8].qvalue <= 136))) { + result[0] += -107.08335478914324; + } else { + result[0] += -12.489971356584766; + } + } + } + } else { + if (LIKELY(false || (data[3].qvalue <= 180))) { + result[0] += -405.35269670345906; + } else { + result[0] += -207.23569477608535; + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (LIKELY(false || (data[6].qvalue <= 180))) { + if (LIKELY(false || (data[0].qvalue <= 392))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (LIKELY(false || (data[0].qvalue <= 292))) { + if (UNLIKELY(false || (data[3].qvalue <= 34))) { + result[0] += -26.43222222269896; + } else { + result[0] += 7.909229573729924; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 30))) { + result[0] += 8.617729518857145; + } else { + result[0] += 108.90607449231234; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 74))) { + if (UNLIKELY(false || (data[10].qvalue <= 70))) { + result[0] += -1.9284671174533046; + } else { + result[0] += -105.2363711810878; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 106))) { + result[0] += 35.07140832310961; + } else { + result[0] += -52.187791710622804; + } + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 222))) { + if (UNLIKELY(false || (data[2].qvalue <= 86))) { + if (LIKELY(false || (data[2].qvalue <= 64))) { + result[0] += 53.903161702785724; + } else { + result[0] += -171.4159662862328; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 114))) { + result[0] += 123.56275048732999; + } else { + result[0] += 26.26029225107184; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -422.00141694181985; + } else { + result[0] += -107.9116343442738; + } + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 88))) { + if (LIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -288.46097298039064; + } else { + if (LIKELY(false || (data[7].qvalue <= 196))) { + result[0] += 375.28961351398226; + } else { + result[0] += -281.2952544835409; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 446))) { + result[0] += -17.901971338973112; + } else { + if (LIKELY(false || (data[6].qvalue <= 186))) { + result[0] += -312.93686064175296; + } else { + result[0] += -716.0857770302855; + } + } + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 88))) { + result[0] += 451.3160600734765; + } else { + if (UNLIKELY(false || (data[10].qvalue <= 54))) { + if (UNLIKELY(false || (data[8].qvalue <= 16))) { + result[0] += 120.12627461414955; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -1770.8570323768029; + } else { + result[0] += -297.6523306745511; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 184))) { + if (LIKELY(false || (data[7].qvalue <= 196))) { + if (LIKELY(false || (data[0].qvalue <= 472))) { + result[0] += 199.35980767050484; + } else { + result[0] += 503.56640093198286; + } + } else { + result[0] += -518.0686588935853; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + if (LIKELY(false || (data[7].qvalue <= 188))) { + result[0] += -150.74680039702164; + } else { + result[0] += -1055.9052195002068; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 176))) { + result[0] += -46.963809276019184; + } else { + result[0] += 394.9533442330038; + } + } + } + } + } + } + if (LIKELY(false || (data[10].qvalue <= 120))) { + if (LIKELY(false || (data[5].qvalue <= 84))) { + if (LIKELY(false || (data[6].qvalue <= 72))) { + if (LIKELY(false || (data[1].qvalue <= 96))) { + if (LIKELY(false || (data[1].qvalue <= 92))) { + if (LIKELY(false || (data[8].qvalue <= 116))) { + result[0] += 14.869440540791798; + } else { + result[0] += -36.73557311757522; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 64))) { + result[0] += -538.0900500719082; + } else { + result[0] += -27.241161506715144; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 174))) { + result[0] += 132.0541996118715; + } else { + result[0] += -113.04252572422735; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 24))) { + if (LIKELY(false || (data[8].qvalue <= 68))) { + if (LIKELY(false || (data[1].qvalue <= 160))) { + result[0] += 244.86366814112222; + } else { + result[0] += -95.24641504542937; + } + } else { + result[0] += -76.8715657600925; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 114))) { + if (LIKELY(false || (data[5].qvalue <= 76))) { + result[0] += -120.29792651308429; + } else { + result[0] += 5.15502948111146; + } + } else { + result[0] += -227.26424899945584; + } + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 48))) { + if (LIKELY(false || (data[10].qvalue <= 72))) { + if (LIKELY(false || (data[6].qvalue <= 126))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += -511.47464005858114; + } else { + result[0] += 99.31151638687581; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 2))) { + result[0] += 139.52958863442427; + } else { + result[0] += -222.6339569962306; + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 112))) { + result[0] += -706.5550247771737; + } else { + result[0] += 32.62538379320284; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 152))) { + if (LIKELY(false || (data[8].qvalue <= 150))) { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + result[0] += -225.27583884895458; + } else { + result[0] += 97.88878371179209; + } + } else { + result[0] += -91.48452493663109; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 156))) { + if (UNLIKELY(false || (data[1].qvalue <= 148))) { + result[0] += -972.0166516770522; + } else { + result[0] += -3.8444739361171365; + } + } else { + result[0] += -30.275824314188043; + } + } + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 136))) { + if (LIKELY(false || (data[1].qvalue <= 128))) { + result[0] += -591.4492984187848; + } else { + result[0] += 52.69617940013822; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 86))) { + if (LIKELY(false || (data[2].qvalue <= 180))) { + result[0] += 84.41514196973255; + } else { + result[0] += -58.25664931323567; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 6))) { + result[0] += 79.8372134768803; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 190))) { + result[0] += -166.72699212355798; + } else { + result[0] += -39.28083649832746; + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 436))) { + if (LIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[0].qvalue <= 384))) { + if (LIKELY(false || (data[1].qvalue <= 126))) { + if (LIKELY(false || (data[4].qvalue <= 102))) { + if (LIKELY(false || (data[1].qvalue <= 102))) { + result[0] += 0.7528430355879543; + } else { + result[0] += -81.79712338566776; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 108))) { + result[0] += 169.93142796462274; + } else { + result[0] += -185.79991240603783; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 126))) { + if (LIKELY(false || (data[2].qvalue <= 120))) { + result[0] += -28.331180770557967; + } else { + result[0] += 672.7509955188011; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 274))) { + result[0] += -114.78306332631581; + } else { + result[0] += -408.53260808925637; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 198))) { + if (LIKELY(false || (data[6].qvalue <= 128))) { + if (UNLIKELY(false || (data[9].qvalue <= 16))) { + result[0] += -1018.0208814686271; + } else { + result[0] += 59.74134293981621; + } + } else { + result[0] += 293.35493295005193; + } + } else { + result[0] += -400.56582852441784; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 98))) { + if (UNLIKELY(false || (data[9].qvalue <= 14))) { + if (LIKELY(false || (data[0].qvalue <= 420))) { + result[0] += -74.3543481633016; + } else { + if (LIKELY(false || (data[1].qvalue <= 150))) { + result[0] += 571.9786783643856; + } else { + result[0] += -219.12600540624658; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 342))) { + result[0] += -101.20421903443618; + } else { + result[0] += -425.1879279279357; + } + } + } else { + result[0] += -38.8853994688072; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 0))) { + result[0] += -129.9772116102948; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 100))) { + if (UNLIKELY(false || (data[7].qvalue <= 56))) { + result[0] += 352.2167561334443; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 452))) { + if (LIKELY(false || (data[4].qvalue <= 104))) { + result[0] += -179.85826237208437; + } else { + result[0] += -1026.3738449938005; + } + } else { + result[0] += 86.91047265123943; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 140))) { + if (UNLIKELY(false || (data[9].qvalue <= 10))) { + if (UNLIKELY(false || (data[0].qvalue <= 448))) { + result[0] += -194.66018635911382; + } else { + result[0] += 165.22462004866262; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 112))) { + result[0] += 280.5581946286645; + } else { + result[0] += 803.432975071445; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 464))) { + if (LIKELY(false || (data[2].qvalue <= 218))) { + result[0] += 2.697588396559468; + } else { + result[0] += -478.252898298942; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 154))) { + result[0] += -208.4543974390897; + } else { + result[0] += 185.3089271840308; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 426))) { + if (LIKELY(false || (data[1].qvalue <= 124))) { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (LIKELY(false || (data[5].qvalue <= 86))) { + if (LIKELY(false || (data[6].qvalue <= 76))) { + if (LIKELY(false || (data[4].qvalue <= 80))) { + result[0] += -1.486803701497208; + } else { + result[0] += 82.51456493961574; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 26))) { + result[0] += -1724.8354696321032; + } else { + result[0] += -82.71048025173705; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 22))) { + if (UNLIKELY(false || (data[4].qvalue <= 110))) { + result[0] += -790.0620438667581; + } else { + result[0] += 106.26605184431772; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 378))) { + result[0] += 33.9153156537546; + } else { + result[0] += 173.09121370877705; + } + } + } + } else { + result[0] += -154.93054492116286; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 54))) { + if (LIKELY(false || (data[2].qvalue <= 50))) { + if (LIKELY(false || (data[2].qvalue <= 42))) { + if (LIKELY(false || (data[0].qvalue <= 392))) { + result[0] += -1.9879083035835015; + } else { + result[0] += 398.5109346540218; + } + } else { + result[0] += -786.5885010553891; + } + } else { + result[0] += 430.4466811037737; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 342))) { + result[0] += -47.917740653423564; + } else { + if (LIKELY(false || (data[0].qvalue <= 412))) { + if (LIKELY(false || (data[2].qvalue <= 164))) { + result[0] += -361.3887876613474; + } else { + result[0] += -59.68743934462101; + } + } else { + result[0] += -24.34286763077365; + } + } + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 10))) { + result[0] += 711.5911056007745; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 88))) { + if (LIKELY(false || (data[0].qvalue <= 454))) { + if (LIKELY(false || (data[2].qvalue <= 64))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += -939.5055510805191; + } else { + result[0] += 49.38415531008706; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 128))) { + result[0] += -607.7336297438973; + } else { + result[0] += 231.05599252528052; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 40))) { + result[0] += -676.7676630675595; + } else { + result[0] += 107.56624878796568; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 104))) { + if (UNLIKELY(false || (data[3].qvalue <= 110))) { + if (UNLIKELY(false || (data[7].qvalue <= 52))) { + result[0] += 470.39671526527695; + } else { + result[0] += 19.64177427856826; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 456))) { + result[0] += 257.8561111510292; + } else { + result[0] += -156.72749393540863; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 112))) { + result[0] += 541.0799146065921; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 444))) { + result[0] += -223.96127054701944; + } else { + result[0] += 35.39412132031614; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 458))) { + if (LIKELY(false || (data[2].qvalue <= 200))) { + if (LIKELY(false || (data[3].qvalue <= 172))) { + if (LIKELY(false || (data[2].qvalue <= 198))) { + if (LIKELY(false || (data[0].qvalue <= 392))) { + if (LIKELY(false || (data[2].qvalue <= 180))) { + result[0] += -1.1948610469474203; + } else { + result[0] += -80.88158309915174; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 10))) { + result[0] += -96.14071101510345; + } else { + result[0] += 63.358216911141255; + } + } + } else { + result[0] += 176.15313222545365; + } + } else { + result[0] += -326.1906626097021; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 116))) { + if (LIKELY(false || (data[0].qvalue <= 430))) { + if (UNLIKELY(false || (data[8].qvalue <= 146))) { + if (LIKELY(false || (data[0].qvalue <= 400))) { + result[0] += 40.65129948483353; + } else { + result[0] += 623.8755186699341; + } + } else { + result[0] += -139.52193586780237; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (LIKELY(false || (data[0].qvalue <= 446))) { + result[0] += 215.51009220017923; + } else { + result[0] += 626.7644565408536; + } + } else { + result[0] += -57.77880667756001; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 78))) { + result[0] += -345.1537827658937; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 206))) { + if (UNLIKELY(false || (data[0].qvalue <= 368))) { + result[0] += 120.97750370682058; + } else { + result[0] += -530.0146207114068; + } + } else { + result[0] += -90.90617312134356; + } + } + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 128))) { + if (LIKELY(false || (data[2].qvalue <= 222))) { + if (UNLIKELY(false || (data[8].qvalue <= 44))) { + if (UNLIKELY(false || (data[2].qvalue <= 30))) { + if (LIKELY(false || (data[4].qvalue <= 114))) { + result[0] += 405.6146661410597; + } else { + result[0] += 22.37476339760467; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 78))) { + result[0] += -499.28634142340854; + } else { + result[0] += 68.9305749234034; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 90))) { + if (LIKELY(false || (data[9].qvalue <= 86))) { + result[0] += 529.6389184175102; + } else { + result[0] += 95.91732717933235; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 158))) { + result[0] += 194.2271286977213; + } else { + result[0] += -22.243321533686604; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + result[0] += -322.8706246634995; + } else { + result[0] += 171.93787845774688; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -280.2205757227194; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 16))) { + result[0] += 805.6007065468135; + } else { + if (LIKELY(false || (data[9].qvalue <= 16))) { + if (LIKELY(false || (data[7].qvalue <= 192))) { + result[0] += 103.74300879356734; + } else { + result[0] += -219.28913112606025; + } + } else { + result[0] += -516.2170829126527; + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 54))) { + if (UNLIKELY(false || (data[0].qvalue <= 0))) { + result[0] += -98.93297408058446; + } else { + if (LIKELY(false || (data[4].qvalue <= 60))) { + if (LIKELY(false || (data[3].qvalue <= 126))) { + if (UNLIKELY(false || (data[0].qvalue <= 18))) { + result[0] += -35.246449455925166; + } else { + result[0] += -11.913885864248398; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 12))) { + result[0] += 244.38629528569243; + } else { + result[0] += -59.63764056448275; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 136))) { + result[0] += -67.53814213480176; + } else { + result[0] += 13.863392069333836; + } + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + if (LIKELY(false || (data[1].qvalue <= 10))) { + if (LIKELY(false || (data[0].qvalue <= 232))) { + if (UNLIKELY(false || (data[2].qvalue <= 24))) { + result[0] += 243.2273344714578; + } else { + if (LIKELY(false || (data[2].qvalue <= 52))) { + result[0] += 56.08540095837141; + } else { + result[0] += -73.3251905895068; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 128))) { + if (UNLIKELY(false || (data[2].qvalue <= 24))) { + result[0] += 643.1260108343162; + } else { + result[0] += 308.232898319556; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 318))) { + result[0] += -85.94227185477129; + } else { + result[0] += 339.32448533355216; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 196))) { + result[0] += 295.57345918020815; + } else { + result[0] += 541.3180556951017; + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 156))) { + if (UNLIKELY(false || (data[4].qvalue <= 6))) { + if (LIKELY(false || (data[4].qvalue <= 4))) { + if (UNLIKELY(false || (data[6].qvalue <= 2))) { + result[0] += 176.28854142574565; + } else { + result[0] += -16.26659984192861; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 224))) { + result[0] += 272.5245026026528; + } else { + result[0] += 630.8310847742418; + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 10))) { + if (LIKELY(false || (data[10].qvalue <= 76))) { + result[0] += -99.36810379968507; + } else { + result[0] += -462.5017447936385; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 146))) { + result[0] += 3.9186464796296914; + } else { + result[0] += 225.22750105396563; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 364))) { + if (UNLIKELY(false || (data[0].qvalue <= 178))) { + if (LIKELY(false || (data[1].qvalue <= 22))) { + result[0] += -168.88397604209945; + } else { + result[0] += 38.12118179055833; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 14))) { + result[0] += -208.349456121144; + } else { + result[0] += -434.7176030497106; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 428))) { + if (LIKELY(false || (data[1].qvalue <= 22))) { + result[0] += 147.36975160660015; + } else { + result[0] += -427.1472748332084; + } + } else { + result[0] += 468.6802177857324; + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (LIKELY(false || (data[5].qvalue <= 118))) { + if (LIKELY(false || (data[0].qvalue <= 438))) { + if (LIKELY(false || (data[7].qvalue <= 194))) { + if (LIKELY(false || (data[1].qvalue <= 130))) { + if (LIKELY(false || (data[4].qvalue <= 106))) { + result[0] += 0.3368638092539733; + } else { + result[0] += 173.775034577441; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 60))) { + result[0] += 48.85885497450373; + } else { + result[0] += -96.87483192360062; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 346))) { + result[0] += -78.03276293917031; + } else { + if (LIKELY(false || (data[0].qvalue <= 428))) { + result[0] += -418.6164374426745; + } else { + result[0] += -98.44042608027418; + } + } + } + } else { + if (LIKELY(false || (data[5].qvalue <= 112))) { + if (UNLIKELY(false || (data[9].qvalue <= 8))) { + if (UNLIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -275.91209245174423; + } else { + result[0] += 99.25590915465185; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 82))) { + result[0] += -41.70434165862219; + } else { + result[0] += 130.83735776026924; + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 4))) { + if (UNLIKELY(false || (data[0].qvalue <= 444))) { + result[0] += 208.90021667361708; + } else { + result[0] += 614.78246500265; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 452))) { + result[0] += -186.66325247313335; + } else { + result[0] += 252.69463305407146; + } + } + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 158))) { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -243.87323720092883; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 160))) { + result[0] += 607.3841937390166; + } else { + if (LIKELY(false || (data[2].qvalue <= 224))) { + result[0] += -4.642945887934479; + } else { + result[0] += -425.1676612575311; + } + } + } + } else { + result[0] += -226.78722863564013; + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 88))) { + if (LIKELY(false || (data[2].qvalue <= 226))) { + result[0] += 457.093631103365; + } else { + result[0] += -195.01605350860177; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 54))) { + if (UNLIKELY(false || (data[8].qvalue <= 16))) { + result[0] += 121.71032578695974; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -1535.090860877404; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 48))) { + result[0] += -642.94729679988; + } else { + result[0] += -89.58887867028852; + } + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 88))) { + if (UNLIKELY(false || (data[5].qvalue <= 72))) { + result[0] += -178.884099836437; + } else { + result[0] += 349.2407402511536; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 144))) { + result[0] += -1194.8087333496094; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -154.55357593204099; + } else { + result[0] += 257.1888927003237; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 112))) { + if (UNLIKELY(false || (data[0].qvalue <= 0))) { + result[0] += -89.14531598408416; + } else { + result[0] += -18.084829562955424; + } + } else { + if (LIKELY(false || (data[8].qvalue <= 138))) { + if (LIKELY(false || (data[3].qvalue <= 116))) { + if (LIKELY(false || (data[8].qvalue <= 118))) { + if (UNLIKELY(false || (data[4].qvalue <= 38))) { + if (LIKELY(false || (data[1].qvalue <= 44))) { + result[0] += 16.943599637772344; + } else { + result[0] += 106.06836417622998; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 42))) { + result[0] += -528.7742969051144; + } else { + result[0] += -9.291722918418776; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 136))) { + if (LIKELY(false || (data[2].qvalue <= 132))) { + result[0] += -118.09966496394628; + } else { + result[0] += -560.1406224711944; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 140))) { + result[0] += -51.02960535934701; + } else { + result[0] += 338.4129630811006; + } + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 104))) { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + if (LIKELY(false || (data[0].qvalue <= 464))) { + result[0] += -248.21126531471361; + } else { + result[0] += 386.6061213337075; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 376))) { + result[0] += 94.34550988961043; + } else { + result[0] += 304.2723529855085; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 436))) { + if (LIKELY(false || (data[8].qvalue <= 106))) { + result[0] += -8.896943765616806; + } else { + result[0] += -245.20061258455573; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 60))) { + result[0] += -113.87022533457596; + } else { + result[0] += 116.77525773183898; + } + } + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 152))) { + if (LIKELY(false || (data[0].qvalue <= 428))) { + if (UNLIKELY(false || (data[0].qvalue <= 224))) { + result[0] += -624.1711191522509; + } else { + if (LIKELY(false || (data[0].qvalue <= 414))) { + result[0] += -1347.2302644189722; + } else { + result[0] += -733.7942435464515; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 60))) { + result[0] += 471.0712008104827; + } else { + result[0] += -142.56709666372134; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + if (UNLIKELY(false || (data[9].qvalue <= 104))) { + if (UNLIKELY(false || (data[2].qvalue <= 192))) { + result[0] += -1567.6200324035817; + } else { + result[0] += -98.50162535806925; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 398))) { + result[0] += -50.25901125894962; + } else { + result[0] += 60.40281064144929; + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 102))) { + if (LIKELY(false || (data[3].qvalue <= 178))) { + result[0] += 379.87726438502557; + } else { + result[0] += -199.4001040982219; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -287.5563657821928; + } else { + result[0] += 332.3956673905034; + } + } + } + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 232))) { + if (UNLIKELY(false || (data[4].qvalue <= 36))) { + if (LIKELY(false || (data[1].qvalue <= 40))) { + if (LIKELY(false || (data[4].qvalue <= 30))) { + result[0] += -1.565030040146726; + } else { + result[0] += -104.9566604701055; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 138))) { + if (UNLIKELY(false || (data[0].qvalue <= 76))) { + result[0] += -17.037935890389736; + } else { + result[0] += 63.289328847278654; + } + } else { + result[0] += -1324.6271531918176; + } + } + } else { + result[0] += -20.81789198275515; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 34))) { + if (LIKELY(false || (data[8].qvalue <= 100))) { + if (LIKELY(false || (data[8].qvalue <= 98))) { + if (LIKELY(false || (data[2].qvalue <= 134))) { + if (UNLIKELY(false || (data[0].qvalue <= 290))) { + result[0] += 2.015327035742837; + } else { + result[0] += 76.49554789592639; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 402))) { + result[0] += -823.5010456874027; + } else { + result[0] += 182.88330508869717; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 376))) { + result[0] += -684.3245220762328; + } else { + result[0] += 114.75508963752516; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 92))) { + result[0] += 584.9961795342024; + } else { + if (UNLIKELY(false || (data[9].qvalue <= 118))) { + result[0] += 284.6160485461698; + } else { + if (LIKELY(false || (data[0].qvalue <= 342))) { + result[0] += -0.3173057744465385; + } else { + result[0] += 255.28193335274855; + } + } + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 44))) { + if (UNLIKELY(false || (data[5].qvalue <= 24))) { + if (LIKELY(false || (data[3].qvalue <= 18))) { + if (LIKELY(false || (data[0].qvalue <= 368))) { + result[0] += -109.2669803718638; + } else { + result[0] += 49.7918489437198; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 26))) { + result[0] += -35.33314939837475; + } else { + result[0] += 383.1659807220039; + } + } + } else { + if (UNLIKELY(false || (data[11].qvalue <= 0))) { + if (LIKELY(false || (data[8].qvalue <= 80))) { + result[0] += -119.6036066769413; + } else { + result[0] += -530.947990035426; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + result[0] += -80.63511119863223; + } else { + result[0] += 415.2734672989832; + } + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 8))) { + if (UNLIKELY(false || (data[3].qvalue <= 68))) { + result[0] += 704.1141519325657; + } else { + if (UNLIKELY(false || (data[8].qvalue <= 2))) { + result[0] += 84.60147882812862; + } else { + result[0] += -187.8254210699395; + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 70))) { + if (LIKELY(false || (data[5].qvalue <= 90))) { + result[0] += 121.89790445304102; + } else { + result[0] += -115.0763981218671; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 72))) { + result[0] += -654.9303551006227; + } else { + result[0] += 7.981521248472231; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 420))) { + if (LIKELY(false || (data[6].qvalue <= 144))) { + if (LIKELY(false || (data[10].qvalue <= 146))) { + if (LIKELY(false || (data[2].qvalue <= 210))) { + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (LIKELY(false || (data[0].qvalue <= 358))) { + result[0] += -2.9256693373180407; + } else { + result[0] += 52.86899999050905; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 162))) { + result[0] += -236.3933458780788; + } else { + result[0] += -14.339195301019586; + } + } + } else { + result[0] += -219.14539537485498; + } + } else { + result[0] += -318.42234277715806; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 154))) { + if (UNLIKELY(false || (data[0].qvalue <= 274))) { + result[0] += -22.289702159300546; + } else { + if (UNLIKELY(false || (data[1].qvalue <= 42))) { + result[0] += 180.36521926437015; + } else { + result[0] += -260.09045231531485; + } + } + } else { + result[0] += 44.37903776463972; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 86))) { + if (UNLIKELY(false || (data[0].qvalue <= 440))) { + if (UNLIKELY(false || (data[7].qvalue <= 46))) { + if (LIKELY(false || (data[1].qvalue <= 90))) { + result[0] += -68.81869559889574; + } else { + result[0] += 716.3498421781522; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 106))) { + if (LIKELY(false || (data[4].qvalue <= 78))) { + result[0] += -171.83362174216776; + } else { + result[0] += -984.3537857945884; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 100))) { + result[0] += -338.7075403591485; + } else { + result[0] += 123.96422662235261; + } + } + } + } else { + if (LIKELY(false || (data[3].qvalue <= 164))) { + if (LIKELY(false || (data[9].qvalue <= 126))) { + if (UNLIKELY(false || (data[0].qvalue <= 454))) { + result[0] += -204.18509525988378; + } else { + result[0] += 64.71659005621359; + } + } else { + result[0] += 817.3359237308946; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 26))) { + result[0] += -262.7801965080877; + } else { + result[0] += 612.6525648856328; + } + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 140))) { + if (LIKELY(false || (data[8].qvalue <= 128))) { + if (LIKELY(false || (data[6].qvalue <= 150))) { + if (LIKELY(false || (data[1].qvalue <= 148))) { + result[0] += 369.7289746082688; + } else { + result[0] += -103.32913617071652; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 22))) { + result[0] += -25.94812018229829; + } else { + result[0] += 299.29536905910857; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + result[0] += 18.803068756821325; + } else { + result[0] += 861.4705320103184; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 118))) { + result[0] += 148.92751319365524; + } else { + if (UNLIKELY(false || (data[6].qvalue <= 126))) { + if (UNLIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -1775.7163414874296; + } else { + result[0] += 74.55151140491274; + } + } else { + result[0] += -10.06076847351882; + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 450))) { + if (LIKELY(false || (data[1].qvalue <= 148))) { + if (UNLIKELY(false || (data[10].qvalue <= 30))) { + if (LIKELY(false || (data[6].qvalue <= 152))) { + if (UNLIKELY(false || (data[9].qvalue <= 92))) { + if (LIKELY(false || (data[8].qvalue <= 12))) { + result[0] += 21.302765815704674; + } else { + result[0] += -269.2691797738821; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 14))) { + result[0] += -63.12874563850693; + } else { + result[0] += 62.93227886630939; + } + } + } else { + result[0] += -400.8612725252076; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 28))) { + if (LIKELY(false || (data[0].qvalue <= 424))) { + if (LIKELY(false || (data[0].qvalue <= 386))) { + result[0] += 58.49853608139253; + } else { + result[0] += 379.42843601568325; + } + } else { + result[0] += -1179.5767049009407; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 30))) { + if (LIKELY(false || (data[1].qvalue <= 100))) { + result[0] += 19.778425640043217; + } else { + result[0] += -856.1976997050459; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 424))) { + result[0] += -5.97846202630444; + } else { + result[0] += 86.90154167537219; + } + } + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 102))) { + result[0] += 410.7033838491514; + } else { + if (UNLIKELY(false || (data[4].qvalue <= 112))) { + result[0] += -278.12154040332797; + } else { + result[0] += -70.26805980080006; + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 180))) { + if (LIKELY(false || (data[4].qvalue <= 136))) { + if (UNLIKELY(false || (data[8].qvalue <= 46))) { + if (LIKELY(false || (data[9].qvalue <= 52))) { + if (UNLIKELY(false || (data[4].qvalue <= 108))) { + result[0] += 202.68141819077738; + } else { + result[0] += -167.2132011812022; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -1384.5104673530457; + } else { + result[0] += -189.4566731860187; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 140))) { + if (LIKELY(false || (data[7].qvalue <= 132))) { + result[0] += 187.09927614728926; + } else { + result[0] += 567.0117546161409; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -93.51709182438407; + } else { + result[0] += 121.54129938504501; + } + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 140))) { + result[0] += 465.57152458676364; + } else { + result[0] += -479.2019554780659; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 472))) { + if (LIKELY(false || (data[10].qvalue <= 106))) { + if (LIKELY(false || (data[7].qvalue <= 200))) { + if (UNLIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -220.95806736832415; + } else { + result[0] += 116.17247093054512; + } + } else { + result[0] += -729.6717337371826; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 164))) { + result[0] += -312.7238552339285; + } else { + result[0] += -1140.467401624576; + } + } + } else { + result[0] += 195.02478855827175; + } + } + } + if (LIKELY(false || (data[8].qvalue <= 154))) { + if (LIKELY(false || (data[0].qvalue <= 392))) { + if (LIKELY(false || (data[6].qvalue <= 62))) { + if (LIKELY(false || (data[0].qvalue <= 310))) { + if (LIKELY(false || (data[8].qvalue <= 120))) { + if (LIKELY(false || (data[8].qvalue <= 100))) { + result[0] += -9.71572720333726; + } else { + result[0] += 67.59923309810226; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 52))) { + result[0] += -351.55140420047667; + } else { + result[0] += -16.616159911071314; + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 72))) { + if (LIKELY(false || (data[4].qvalue <= 66))) { + result[0] += 69.88081147655924; + } else { + result[0] += -969.7781215245079; + } + } else { + result[0] += 228.67764875201647; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 4))) { + if (UNLIKELY(false || (data[10].qvalue <= 2))) { + result[0] += 74.35381121983716; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 138))) { + result[0] += -62.154687912270276; + } else { + result[0] += -796.2456169363136; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 56))) { + if (LIKELY(false || (data[4].qvalue <= 86))) { + result[0] += -156.1506230350774; + } else { + result[0] += 119.82853149552272; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 106))) { + result[0] += 13.900388157788383; + } else { + result[0] += -42.87238835775265; + } + } + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 78))) { + if (UNLIKELY(false || (data[6].qvalue <= 58))) { + if (LIKELY(false || (data[6].qvalue <= 56))) { + if (LIKELY(false || (data[2].qvalue <= 156))) { + result[0] += -25.79300413884613; + } else { + result[0] += 449.6201546856599; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 424))) { + result[0] += -1246.7840846746014; + } else { + result[0] += -60.57121668348967; + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 18))) { + if (UNLIKELY(false || (data[0].qvalue <= 416))) { + result[0] += -507.2811346565345; + } else { + result[0] += 1.6959367259683802; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 130))) { + result[0] += 256.5119388340715; + } else { + result[0] += 61.901222326179735; + } + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 4))) { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -712.841039622884; + } else { + result[0] += -50.285859444198785; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 168))) { + if (UNLIKELY(false || (data[7].qvalue <= 80))) { + result[0] += -89.82284215276091; + } else { + result[0] += 57.2312430185315; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 462))) { + result[0] += -196.41216488031796; + } else { + result[0] += 64.87819986422424; + } + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 422))) { + if (LIKELY(false || (data[0].qvalue <= 352))) { + result[0] += -54.40201249787878; + } else { + result[0] += -323.09540305923275; + } + } else { + result[0] += 22.58190522531515; + } + } + if (LIKELY(false || (data[7].qvalue <= 192))) { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (LIKELY(false || (data[2].qvalue <= 212))) { + if (LIKELY(false || (data[0].qvalue <= 396))) { + if (UNLIKELY(false || (data[9].qvalue <= 62))) { + if (UNLIKELY(false || (data[1].qvalue <= 34))) { + result[0] += 320.8771929320019; + } else { + result[0] += -48.74134204044459; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 64))) { + result[0] += 144.15507685887556; + } else { + result[0] += 2.061937768452418; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 168))) { + if (LIKELY(false || (data[8].qvalue <= 142))) { + result[0] += 14.310048813060831; + } else { + result[0] += -891.4821913653136; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 186))) { + result[0] += 230.68364280672665; + } else { + result[0] += 31.828982825162395; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 460))) { + if (UNLIKELY(false || (data[8].qvalue <= 148))) { + if (LIKELY(false || (data[0].qvalue <= 446))) { + result[0] += 5.3923912609412765; + } else { + result[0] += -296.2815883663324; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 18))) { + result[0] += 0.33503809236265447; + } else { + result[0] += -222.23518809470139; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 162))) { + result[0] += 435.30908950076656; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 220))) { + result[0] += 151.71039930723558; + } else { + result[0] += -220.68651987712957; + } + } + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 158))) { + if (LIKELY(false || (data[5].qvalue <= 106))) { + if (LIKELY(false || (data[8].qvalue <= 34))) { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -761.8192641261176; + } else { + result[0] += -24.696202075383436; + } + } else { + result[0] += 297.8870552382013; + } + } else { + result[0] += -1111.3451507308962; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 180))) { + result[0] += 399.4329665421262; + } else { + if (LIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -33.631317828261764; + } else { + if (LIKELY(false || (data[6].qvalue <= 186))) { + result[0] += 521.4533408866705; + } else { + result[0] += -47.855811708641056; + } + } + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 438))) { + if (UNLIKELY(false || (data[0].qvalue <= 352))) { + result[0] += -42.87267583810598; + } else { + result[0] += -282.3784003570174; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 132))) { + result[0] += 156.2716050426377; + } else { + if (UNLIKELY(false || (data[8].qvalue <= 144))) { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + if (UNLIKELY(false || (data[2].qvalue <= 154))) { + result[0] += -106.3379635111628; + } else { + result[0] += -799.4368728340669; + } + } else { + result[0] += -30.901543195994442; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -278.9996333283842; + } else { + result[0] += 97.21542436889949; + } + } + } + } + } + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + if (LIKELY(false || (data[0].qvalue <= 198))) { + if (LIKELY(false || (data[1].qvalue <= 10))) { + if (UNLIKELY(false || (data[2].qvalue <= 24))) { + if (UNLIKELY(false || (data[0].qvalue <= 62))) { + result[0] += -71.54944060418909; + } else { + result[0] += 200.91246808639335; + } + } else { + result[0] += -16.524995964984008; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 56))) { + result[0] += -6.615663904946698; + } else { + result[0] += 277.0038505094405; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 128))) { + if (LIKELY(false || (data[1].qvalue <= 10))) { + result[0] += 242.12226853180888; + } else { + result[0] += 495.40205234661437; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 308))) { + result[0] += -144.40748272556144; + } else { + result[0] += 244.60940232429508; + } + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 156))) { + if (UNLIKELY(false || (data[4].qvalue <= 6))) { + if (LIKELY(false || (data[4].qvalue <= 4))) { + if (LIKELY(false || (data[9].qvalue <= 144))) { + if (UNLIKELY(false || (data[10].qvalue <= 12))) { + result[0] += 151.95679595717183; + } else { + result[0] += -75.40462882328015; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 248))) { + result[0] += 59.16238539562008; + } else { + result[0] += 258.58095117618336; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 194))) { + if (UNLIKELY(false || (data[0].qvalue <= 78))) { + result[0] += 34.722618898156114; + } else { + result[0] += 229.7578373765491; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 254))) { + result[0] += 416.11542151307907; + } else { + result[0] += 657.1399509168027; + } + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 10))) { + if (LIKELY(false || (data[0].qvalue <= 396))) { + if (LIKELY(false || (data[10].qvalue <= 76))) { + result[0] += -99.76580517101934; + } else { + result[0] += -406.79586814929917; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 410))) { + result[0] += 67.21864653476916; + } else { + result[0] += 318.3788762612092; + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 152))) { + if (UNLIKELY(false || (data[1].qvalue <= 8))) { + result[0] += -89.91859493216367; + } else { + result[0] += 2.265520650788892; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 182))) { + result[0] += 86.60159851878495; + } else { + result[0] += 446.4563385244049; + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 426))) { + if (UNLIKELY(false || (data[0].qvalue <= 166))) { + result[0] += -85.71042457905949; + } else { + if (LIKELY(false || (data[0].qvalue <= 356))) { + if (LIKELY(false || (data[1].qvalue <= 14))) { + result[0] += -197.6560417052055; + } else { + result[0] += -382.76909364031883; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 22))) { + result[0] += 86.90468637807797; + } else { + result[0] += -409.69153384359265; + } + } + } + } else { + result[0] += 370.0893122985466; + } + } + } + if (LIKELY(false || (data[0].qvalue <= 450))) { + if (LIKELY(false || (data[1].qvalue <= 130))) { + if (LIKELY(false || (data[4].qvalue <= 106))) { + if (UNLIKELY(false || (data[9].qvalue <= 16))) { + result[0] += -778.7936840058063; + } else { + if (UNLIKELY(false || (data[9].qvalue <= 20))) { + result[0] += 194.97101648633375; + } else { + if (UNLIKELY(false || (data[10].qvalue <= 30))) { + result[0] += -35.3509348883947; + } else { + result[0] += 5.30025526653546; + } + } + } + } else { + result[0] += 128.95468067576573; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 106))) { + if (UNLIKELY(false || (data[8].qvalue <= 68))) { + if (LIKELY(false || (data[2].qvalue <= 120))) { + result[0] += -34.18950997644831; + } else { + result[0] += 621.8798392741791; + } + } else { + result[0] += -155.4137360966938; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 108))) { + if (LIKELY(false || (data[0].qvalue <= 430))) { + if (LIKELY(false || (data[7].qvalue <= 108))) { + result[0] += 256.5188225022511; + } else { + result[0] += -99.67316593885579; + } + } else { + result[0] += 620.485300659375; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 100))) { + result[0] += -2286.0077275800704; + } else { + if (LIKELY(false || (data[5].qvalue <= 116))) { + result[0] += -53.309956527903466; + } else { + result[0] += 190.13761434101764; + } + } + } + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 136))) { + if (UNLIKELY(false || (data[2].qvalue <= 78))) { + if (LIKELY(false || (data[1].qvalue <= 142))) { + if (UNLIKELY(false || (data[7].qvalue <= 76))) { + if (LIKELY(false || (data[2].qvalue <= 68))) { + result[0] += 253.43645195632422; + } else { + result[0] += -1413.0786233422066; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 462))) { + result[0] += -690.7561180054086; + } else { + result[0] += -63.707054709028625; + } + } + } else { + result[0] += 291.7091149857328; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 120))) { + if (UNLIKELY(false || (data[8].qvalue <= 128))) { + result[0] += 478.56900909093986; + } else { + result[0] += 167.99342824787573; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 464))) { + result[0] += 60.9554692627185; + } else { + result[0] += -1073.8503825143669; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 464))) { + if (LIKELY(false || (data[4].qvalue <= 136))) { + if (UNLIKELY(false || (data[8].qvalue <= 52))) { + result[0] += -539.7433428985337; + } else { + if (LIKELY(false || (data[1].qvalue <= 160))) { + result[0] += 20.89762446651438; + } else { + result[0] += -416.20782851777005; + } + } + } else { + result[0] += 361.5994213025975; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 172))) { + if (LIKELY(false || (data[1].qvalue <= 160))) { + if (UNLIKELY(false || (data[10].qvalue <= 34))) { + result[0] += 537.4931809409429; + } else { + result[0] += 93.3123402983442; + } + } else { + result[0] += 509.56219742394296; + } + } else { + result[0] += -30.502598904934388; + } + } + } + } + if (UNLIKELY(false || (data[4].qvalue <= 18))) { + if (UNLIKELY(false || (data[9].qvalue <= 108))) { + if (UNLIKELY(false || (data[0].qvalue <= 170))) { + result[0] += 32.02621609860586; + } else { + result[0] += 195.61137056108709; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 116))) { + result[0] += -56.120014772691675; + } else { + if (UNLIKELY(false || (data[9].qvalue <= 120))) { + if (UNLIKELY(false || (data[8].qvalue <= 38))) { + result[0] += 258.16901404153344; + } else { + result[0] += 53.61413424410968; + } + } else { + result[0] += 3.8083516584014774; + } + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 30))) { + if (LIKELY(false || (data[8].qvalue <= 12))) { + if (UNLIKELY(false || (data[10].qvalue <= 2))) { + result[0] += 95.68803265337215; + } else { + if (UNLIKELY(false || (data[10].qvalue <= 8))) { + if (LIKELY(false || (data[3].qvalue <= 132))) { + result[0] += -103.63143270824092; + } else { + result[0] += -416.841719094304; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 452))) { + result[0] += 29.00180398543003; + } else { + result[0] += 739.6504496232433; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 442))) { + if (UNLIKELY(false || (data[9].qvalue <= 44))) { + result[0] += -1170.886850360489; + } else { + if (UNLIKELY(false || (data[9].qvalue <= 78))) { + result[0] += -23.599978133418663; + } else { + result[0] += -233.86794128469188; + } + } + } else { + if (LIKELY(false || (data[3].qvalue <= 100))) { + result[0] += 296.8371355378329; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -578.3205363336284; + } else { + result[0] += 28.31820900489086; + } + } + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 110))) { + if (UNLIKELY(false || (data[2].qvalue <= 34))) { + if (LIKELY(false || (data[0].qvalue <= 270))) { + result[0] += 15.084899225242879; + } else { + if (UNLIKELY(false || (data[6].qvalue <= 44))) { + result[0] += 227.37067720434766; + } else { + result[0] += 79.75576158468904; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 66))) { + if (LIKELY(false || (data[2].qvalue <= 64))) { + result[0] += -21.445175019440377; + } else { + result[0] += -736.583490831738; + } + } else { + if (LIKELY(false || (data[10].qvalue <= 122))) { + result[0] += 19.304014117388515; + } else { + result[0] += -31.807962860341632; + } + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 52))) { + if (LIKELY(false || (data[6].qvalue <= 20))) { + if (LIKELY(false || (data[2].qvalue <= 76))) { + result[0] += -2.770553189772293; + } else { + result[0] += -459.67841100976295; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 402))) { + result[0] += -654.9440592987792; + } else { + result[0] += -113.53853189933453; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 52))) { + result[0] += 141.69815316018148; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 74))) { + result[0] += -502.039703472783; + } else { + result[0] += -32.3855029558517; + } + } + } + } + } + } + if (LIKELY(false || (data[7].qvalue <= 176))) { + if (LIKELY(false || (data[3].qvalue <= 122))) { + if (LIKELY(false || (data[6].qvalue <= 68))) { + if (LIKELY(false || (data[7].qvalue <= 96))) { + if (LIKELY(false || (data[7].qvalue <= 92))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += 92.56955997074317; + } else { + result[0] += -0.9922155287745735; + } + } else { + result[0] += -500.10035950605436; + } + } else { + if (LIKELY(false || (data[10].qvalue <= 82))) { + if (UNLIKELY(false || (data[3].qvalue <= 18))) { + result[0] += -349.0415762351967; + } else { + result[0] += 171.85769845927226; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 156))) { + result[0] += -451.9129687933409; + } else { + result[0] += -27.180822178728683; + } + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 24))) { + if (UNLIKELY(false || (data[5].qvalue <= 52))) { + if (LIKELY(false || (data[6].qvalue <= 170))) { + result[0] += 257.80955723579154; + } else { + result[0] += 14.067277977079232; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 78))) { + result[0] += -187.1084046068555; + } else { + result[0] += -16.47644404213514; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 58))) { + if (UNLIKELY(false || (data[1].qvalue <= 68))) { + result[0] += -459.1713788092935; + } else { + result[0] += -121.28232839389386; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 82))) { + result[0] += 45.911193575042404; + } else { + result[0] += -65.09204602729294; + } + } + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + if (LIKELY(false || (data[8].qvalue <= 148))) { + result[0] += -112.3564328986315; + } else { + result[0] += -581.5931838175455; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 104))) { + if (LIKELY(false || (data[8].qvalue <= 130))) { + if (LIKELY(false || (data[7].qvalue <= 140))) { + result[0] += 99.49014850118782; + } else { + result[0] += 283.8674441857837; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 166))) { + result[0] += -196.33975801441395; + } else { + result[0] += 5.911638064587168; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 128))) { + if (UNLIKELY(false || (data[1].qvalue <= 114))) { + result[0] += 220.39824119047162; + } else { + result[0] += 86.35899353404471; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 136))) { + result[0] += -135.11781706233776; + } else { + result[0] += 1.7254921740277542; + } + } + } + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 32))) { + result[0] += -729.2271141921474; + } else { + if (LIKELY(false || (data[1].qvalue <= 164))) { + if (UNLIKELY(false || (data[8].qvalue <= 6))) { + result[0] += 163.01861591388177; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 182))) { + result[0] += -90.15761066666771; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 184))) { + result[0] += 105.67463769590789; + } else { + result[0] += -40.65848461097406; + } + } + } + } else { + result[0] += -149.36907697589524; + } + } + } + if (LIKELY(false || (data[0].qvalue <= 436))) { + if (UNLIKELY(false || (data[9].qvalue <= 54))) { + if (UNLIKELY(false || (data[2].qvalue <= 14))) { + if (UNLIKELY(false || (data[10].qvalue <= 2))) { + result[0] += 73.61434867220245; + } else { + if (LIKELY(false || (data[4].qvalue <= 92))) { + result[0] += -219.52771218319805; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 182))) { + result[0] += -261.44554618186174; + } else { + result[0] += -1230.408535736753; + } + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 24))) { + if (LIKELY(false || (data[0].qvalue <= 386))) { + if (LIKELY(false || (data[2].qvalue <= 112))) { + result[0] += -5.198946846457009; + } else { + result[0] += 249.1763981732631; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 34))) { + result[0] += 641.3825622819111; + } else { + result[0] += 205.60287406527715; + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 32))) { + if (UNLIKELY(false || (data[4].qvalue <= 126))) { + result[0] += -887.6161494419807; + } else { + result[0] += -171.10035394400404; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 408))) { + result[0] += -58.06284362537737; + } else { + result[0] += 25.218332263091998; + } + } + } + } + } else { + if (LIKELY(false || (data[8].qvalue <= 154))) { + if (LIKELY(false || (data[0].qvalue <= 346))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + if (LIKELY(false || (data[0].qvalue <= 232))) { + result[0] += 25.584773238822454; + } else { + result[0] += 219.8658888633042; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 20))) { + result[0] += -51.10525196126827; + } else { + result[0] += 0.46333939293857984; + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 72))) { + if (LIKELY(false || (data[8].qvalue <= 130))) { + result[0] += 164.15191019337982; + } else { + result[0] += -335.2814106754929; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 84))) { + result[0] += -89.39119339130295; + } else { + result[0] += 34.512913855389506; + } + } + } + } else { + result[0] += -108.08090718754343; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 158))) { + if (UNLIKELY(false || (data[8].qvalue <= 46))) { + if (LIKELY(false || (data[2].qvalue <= 118))) { + if (UNLIKELY(false || (data[7].qvalue <= 76))) { + result[0] += 131.26162098672742; + } else { + if (LIKELY(false || (data[0].qvalue <= 464))) { + result[0] += -442.8722988609171; + } else { + result[0] += 73.24052956825756; + } + } + } else { + result[0] += 686.9491978638054; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 54))) { + result[0] += 687.8496107636925; + } else { + if (LIKELY(false || (data[0].qvalue <= 456))) { + if (UNLIKELY(false || (data[9].qvalue <= 12))) { + result[0] += -77.24307302441612; + } else { + result[0] += 147.57566065994; + } + } else { + if (LIKELY(false || (data[5].qvalue <= 110))) { + result[0] += 138.145849125284; + } else { + result[0] += 445.5193884395197; + } + } + } + } + } else { + result[0] += -13.479785177056158; + } + } + if (LIKELY(false || (data[0].qvalue <= 418))) { + if (LIKELY(false || (data[1].qvalue <= 124))) { + if (LIKELY(false || (data[4].qvalue <= 68))) { + if (LIKELY(false || (data[1].qvalue <= 80))) { + if (UNLIKELY(false || (data[9].qvalue <= 50))) { + result[0] += -577.1611446622182; + } else { + if (LIKELY(false || (data[10].qvalue <= 142))) { + result[0] += 3.8314250912323637; + } else { + result[0] += -329.28055580520333; + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 26))) { + if (UNLIKELY(false || (data[0].qvalue <= 198))) { + result[0] += -194.40768818753736; + } else { + result[0] += -854.4809931534361; + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 70))) { + result[0] += -160.52174054303293; + } else { + result[0] += 2.741586163284305; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 372))) { + if (UNLIKELY(false || (data[1].qvalue <= 46))) { + if (UNLIKELY(false || (data[0].qvalue <= 168))) { + result[0] += 18.984540004822804; + } else { + result[0] += 327.11800611524313; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 20))) { + result[0] += -129.20406003130722; + } else { + result[0] += 17.92278831555766; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 150))) { + if (UNLIKELY(false || (data[2].qvalue <= 6))) { + result[0] += -221.08946446962568; + } else { + result[0] += 229.63359317462343; + } + } else { + result[0] += -223.7223770166941; + } + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 64))) { + if (LIKELY(false || (data[0].qvalue <= 394))) { + result[0] += 5.729375564939926; + } else { + if (LIKELY(false || (data[4].qvalue <= 136))) { + result[0] += 349.6954434120244; + } else { + result[0] += -447.2340327004826; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 346))) { + result[0] += -18.934394820420263; + } else { + if (LIKELY(false || (data[2].qvalue <= 164))) { + if (LIKELY(false || (data[1].qvalue <= 140))) { + result[0] += -512.2810219610269; + } else { + result[0] += -123.2775600600666; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 176))) { + result[0] += 335.8072972282146; + } else { + result[0] += -135.40134282126365; + } + } + } + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 10))) { + result[0] += 408.30240571312294; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 56))) { + result[0] += 203.85368488394235; + } else { + if (UNLIKELY(false || (data[7].qvalue <= 100))) { + if (LIKELY(false || (data[0].qvalue <= 448))) { + if (LIKELY(false || (data[10].qvalue <= 96))) { + result[0] += -457.3360616844447; + } else { + result[0] += 124.47860255821743; + } + } else { + if (LIKELY(false || (data[4].qvalue <= 108))) { + result[0] += 110.48892123347983; + } else { + result[0] += -391.09864189814425; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 114))) { + result[0] += 211.6228610309497; + } else { + if (UNLIKELY(false || (data[10].qvalue <= 100))) { + result[0] += 56.71696470804632; + } else { + result[0] += -35.66565365297374; + } + } + } + } + } + } + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + if (LIKELY(false || (data[1].qvalue <= 10))) { + if (LIKELY(false || (data[2].qvalue <= 52))) { + if (UNLIKELY(false || (data[2].qvalue <= 24))) { + result[0] += 147.91960717089773; + } else { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + if (LIKELY(false || (data[2].qvalue <= 46))) { + result[0] += 43.27359384398152; + } else { + result[0] += 111.57729094083193; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 8))) { + result[0] += 113.34997104546899; + } else { + result[0] += 72.55342688666062; + } + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 128))) { + if (LIKELY(false || (data[1].qvalue <= 8))) { + if (LIKELY(false || (data[1].qvalue <= 6))) { + result[0] += 31.246573605653715; + } else { + result[0] += 46.606138483456206; + } + } else { + result[0] += 25.9373059644741; + } + } else { + result[0] += -31.626364376909578; + } + } + } else { + result[0] += 240.78696773473405; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 156))) { + if (UNLIKELY(false || (data[4].qvalue <= 6))) { + if (LIKELY(false || (data[4].qvalue <= 4))) { + if (LIKELY(false || (data[1].qvalue <= 20))) { + if (UNLIKELY(false || (data[9].qvalue <= 136))) { + result[0] += -48.976438574076354; + } else { + result[0] += 84.65299344607978; + } + } else { + result[0] += -130.88113764489268; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 72))) { + if (UNLIKELY(false || (data[1].qvalue <= 22))) { + result[0] += 338.1827440503927; + } else { + result[0] += 298.08608821916727; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 22))) { + result[0] += 204.95094375285473; + } else { + result[0] += 165.99111228283587; + } + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 10))) { + if (LIKELY(false || (data[10].qvalue <= 76))) { + if (LIKELY(false || (data[2].qvalue <= 92))) { + result[0] += -108.03954054155902; + } else { + result[0] += 14.418807341740987; + } + } else { + result[0] += -321.311952308832; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 146))) { + if (UNLIKELY(false || (data[1].qvalue <= 8))) { + result[0] += -76.25024484457143; + } else { + result[0] += 1.263274912881202; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 6))) { + result[0] += -66.04814325674907; + } else { + result[0] += 205.46869851639258; + } + } + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 36))) { + if (UNLIKELY(false || (data[1].qvalue <= 14))) { + result[0] += -104.7552531611455; + } else { + if (UNLIKELY(false || (data[1].qvalue <= 18))) { + if (LIKELY(false || (data[1].qvalue <= 16))) { + result[0] += -179.2056109143998; + } else { + result[0] += -196.72295793090106; + } + } else { + result[0] += -228.7848599462564; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + result[0] += -62.70206554713903; + } else { + if (UNLIKELY(false || (data[1].qvalue <= 22))) { + result[0] += -109.57286387525; + } else { + result[0] += -86.90439645392793; + } + } + } + } + } + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + if (LIKELY(false || (data[1].qvalue <= 10))) { + if (LIKELY(false || (data[2].qvalue <= 52))) { + if (UNLIKELY(false || (data[2].qvalue <= 24))) { + result[0] += 133.14242400479026; + } else { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + if (LIKELY(false || (data[2].qvalue <= 46))) { + result[0] += 38.96136485201495; + } else { + result[0] += 100.46528939864675; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 8))) { + result[0] += 102.02497851147024; + } else { + result[0] += 65.31936047920384; + } + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 128))) { + if (LIKELY(false || (data[1].qvalue <= 8))) { + if (LIKELY(false || (data[1].qvalue <= 6))) { + result[0] += 28.128267190495155; + } else { + result[0] += 42.006052734820884; + } + } else { + result[0] += 23.349456995785104; + } + } else { + result[0] += -28.466887237596946; + } + } + } else { + result[0] += 216.73249472971176; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 154))) { + if (LIKELY(false || (data[9].qvalue <= 148))) { + if (UNLIKELY(false || (data[10].qvalue <= 2))) { + if (LIKELY(false || (data[2].qvalue <= 156))) { + if (LIKELY(false || (data[9].qvalue <= 114))) { + result[0] += 101.975488812464; + } else { + result[0] += -25.287212545092885; + } + } else { + result[0] += -99.57496398377107; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 30))) { + if (UNLIKELY(false || (data[4].qvalue <= 16))) { + result[0] += 58.55564779565954; + } else { + result[0] += -70.77906117415272; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 18))) { + result[0] += 56.64272064066464; + } else { + result[0] += -1.102421664746258; + } + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 204))) { + if (UNLIKELY(false || (data[4].qvalue <= 4))) { + if (UNLIKELY(false || (data[1].qvalue <= 12))) { + result[0] += 76.95158297864288; + } else { + result[0] += 30.779853089335912; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 12))) { + result[0] += 184.47632299122395; + } else { + result[0] += 240.0561058810638; + } + } + } else { + result[0] += -59.44992973601068; + } + } + } else { + if (LIKELY(false || (data[3].qvalue <= 20))) { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + if (UNLIKELY(false || (data[2].qvalue <= 36))) { + result[0] += 90.32707990001026; + } else { + result[0] += -56.438123774542795; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 92))) { + if (LIKELY(false || (data[2].qvalue <= 88))) { + result[0] += -97.11633504657749; + } else { + result[0] += -327.052109099788; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 24))) { + result[0] += -88.46877715199902; + } else { + result[0] += 36.38893057075731; + } + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 14))) { + result[0] += 164.4902900819997; + } else { + result[0] += 201.51887436344938; + } + } + } + } + if (LIKELY(false || (data[7].qvalue <= 190))) { + if (LIKELY(false || (data[3].qvalue <= 102))) { + if (LIKELY(false || (data[6].qvalue <= 72))) { + if (LIKELY(false || (data[8].qvalue <= 112))) { + if (LIKELY(false || (data[8].qvalue <= 100))) { + if (LIKELY(false || (data[10].qvalue <= 124))) { + result[0] += 2.677490165277401; + } else { + result[0] += -230.8237998800555; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 106))) { + result[0] += 25.99249929961273; + } else { + result[0] += 143.56253715641475; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 158))) { + if (LIKELY(false || (data[4].qvalue <= 32))) { + result[0] += -56.96864498437293; + } else { + result[0] += -361.08262388656516; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 130))) { + result[0] += -1.2571125993192631; + } else { + result[0] += 139.96081546293442; + } + } + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 24))) { + if (LIKELY(false || (data[4].qvalue <= 126))) { + if (LIKELY(false || (data[8].qvalue <= 68))) { + result[0] += 157.68101550278936; + } else { + result[0] += -24.160628623142866; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 100))) { + result[0] += -87.49349951075376; + } else { + result[0] += -651.2774869908153; + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 104))) { + if (UNLIKELY(false || (data[8].qvalue <= 82))) { + result[0] += -72.49802639529806; + } else { + result[0] += -213.66048931924266; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 54))) { + result[0] += 78.72485385773093; + } else { + result[0] += -55.249394110207554; + } + } + } + } + } else { + if (LIKELY(false || (data[10].qvalue <= 134))) { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + if (LIKELY(false || (data[8].qvalue <= 148))) { + result[0] += -94.97855070131733; + } else { + result[0] += -527.4611841577268; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 32))) { + if (LIKELY(false || (data[9].qvalue <= 28))) { + result[0] += 0.9950631307094829; + } else { + result[0] += -188.09164012193523; + } + } else { + if (LIKELY(false || (data[8].qvalue <= 140))) { + result[0] += 64.8246177611995; + } else { + result[0] += -21.226393353713657; + } + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 140))) { + if (UNLIKELY(false || (data[10].qvalue <= 138))) { + result[0] += -69.54835276237708; + } else { + if (LIKELY(false || (data[1].qvalue <= 158))) { + result[0] += -236.0745244061219; + } else { + result[0] += -12.63559195083826; + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 52))) { + result[0] += -236.792523303151; + } else { + if (LIKELY(false || (data[10].qvalue <= 144))) { + result[0] += 52.206924149774636; + } else { + result[0] += -39.51021768049611; + } + } + } + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 140))) { + if (LIKELY(false || (data[2].qvalue <= 226))) { + result[0] += -52.42843817107941; + } else { + result[0] += -306.65785355623103; + } + } else { + result[0] += -385.0662454001109; + } + } + if (LIKELY(false || (data[0].qvalue <= 398))) { + if (LIKELY(false || (data[1].qvalue <= 110))) { + if (LIKELY(false || (data[4].qvalue <= 70))) { + if (LIKELY(false || (data[1].qvalue <= 86))) { + if (UNLIKELY(false || (data[5].qvalue <= 42))) { + if (LIKELY(false || (data[5].qvalue <= 40))) { + result[0] += -11.28284578878582; + } else { + result[0] += -558.1753204017739; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 26))) { + result[0] += 133.21891876863342; + } else { + result[0] += 2.7832862656512685; + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 26))) { + if (LIKELY(false || (data[0].qvalue <= 246))) { + result[0] += -324.1019695078817; + } else { + result[0] += -1005.9270602557989; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 88))) { + result[0] += -134.86731075700266; + } else { + result[0] += 10.66748293923189; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 336))) { + if (LIKELY(false || (data[6].qvalue <= 98))) { + result[0] += 2.8200100312590166; + } else { + result[0] += 101.03470891273177; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 14))) { + if (LIKELY(false || (data[6].qvalue <= 82))) { + result[0] += 102.04557161945678; + } else { + result[0] += -1199.8811078319284; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 178))) { + result[0] += 209.67226520445524; + } else { + result[0] += -245.61747308533063; + } + } + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 60))) { + result[0] += 42.785881512673626; + } else { + if (LIKELY(false || (data[6].qvalue <= 154))) { + if (LIKELY(false || (data[0].qvalue <= 270))) { + result[0] += -45.95588289959001; + } else { + if (UNLIKELY(false || (data[6].qvalue <= 90))) { + result[0] += -6.815612347789953; + } else { + result[0] += -266.0110871919803; + } + } + } else { + result[0] += 39.02030164716074; + } + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 8))) { + result[0] += 324.9267206663035; + } else { + if (LIKELY(false || (data[2].qvalue <= 214))) { + if (UNLIKELY(false || (data[1].qvalue <= 16))) { + result[0] += -112.18559323452085; + } else { + if (LIKELY(false || (data[1].qvalue <= 132))) { + if (UNLIKELY(false || (data[2].qvalue <= 80))) { + result[0] += -56.2055045870944; + } else { + result[0] += 116.07989618400472; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 118))) { + result[0] += 141.36324262903074; + } else { + result[0] += -22.395839800711574; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 458))) { + if (UNLIKELY(false || (data[10].qvalue <= 118))) { + result[0] += 100.73381070844493; + } else { + if (LIKELY(false || (data[6].qvalue <= 174))) { + result[0] += -365.7173433437338; + } else { + result[0] += 21.744193287907777; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 162))) { + result[0] += 280.70190659426964; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 466))) { + result[0] += -456.7905672944539; + } else { + result[0] += 86.74089207808544; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 438))) { + if (LIKELY(false || (data[6].qvalue <= 146))) { + if (LIKELY(false || (data[7].qvalue <= 198))) { + if (LIKELY(false || (data[0].qvalue <= 388))) { + if (LIKELY(false || (data[1].qvalue <= 126))) { + if (LIKELY(false || (data[4].qvalue <= 102))) { + result[0] += -3.0799897635647393; + } else { + result[0] += 83.18986261549072; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 36))) { + result[0] += 225.15234009730352; + } else { + result[0] += -85.63258649231685; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 82))) { + if (LIKELY(false || (data[3].qvalue <= 80))) { + result[0] += 35.515553203945544; + } else { + result[0] += -529.1932694931496; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 124))) { + result[0] += 183.78815671074506; + } else { + result[0] += 39.81733903217535; + } + } + } + } else { + result[0] += -186.6083734780767; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 50))) { + if (UNLIKELY(false || (data[0].qvalue <= 272))) { + result[0] += 16.882292766847286; + } else { + result[0] += -340.80931681790474; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 154))) { + if (LIKELY(false || (data[3].qvalue <= 170))) { + if (LIKELY(false || (data[10].qvalue <= 102))) { + result[0] += -8.494744851894465; + } else { + result[0] += -138.4317062673716; + } + } else { + result[0] += -365.5767086328917; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 172))) { + if (UNLIKELY(false || (data[0].qvalue <= 304))) { + result[0] += -4.6291599966349635; + } else { + result[0] += 233.27933991789052; + } + } else { + result[0] += -47.89610265065409; + } + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 182))) { + if (LIKELY(false || (data[10].qvalue <= 148))) { + if (UNLIKELY(false || (data[8].qvalue <= 46))) { + if (UNLIKELY(false || (data[7].qvalue <= 56))) { + result[0] += 408.84774771757384; + } else { + if (LIKELY(false || (data[4].qvalue <= 136))) { + result[0] += -81.22211370546637; + } else { + result[0] += 149.79791446348088; + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 60))) { + result[0] += 507.31584249730014; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 108))) { + result[0] += 3.6960386503909706; + } else { + result[0] += 132.51686944699037; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 472))) { + if (LIKELY(false || (data[7].qvalue <= 192))) { + if (UNLIKELY(false || (data[0].qvalue <= 460))) { + result[0] += -289.98766205200593; + } else { + result[0] += -37.620573467202114; + } + } else { + result[0] += -1064.0260380735235; + } + } else { + result[0] += 301.66872530386996; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 472))) { + if (LIKELY(false || (data[1].qvalue <= 160))) { + if (LIKELY(false || (data[3].qvalue <= 178))) { + result[0] += -66.47823654447284; + } else { + result[0] += -479.9069173542369; + } + } else { + result[0] += -776.3112829887955; + } + } else { + result[0] += 122.73633086275382; + } + } + } + if (LIKELY(false || (data[0].qvalue <= 458))) { + if (LIKELY(false || (data[6].qvalue <= 168))) { + if (LIKELY(false || (data[6].qvalue <= 166))) { + if (LIKELY(false || (data[6].qvalue <= 164))) { + if (LIKELY(false || (data[0].qvalue <= 424))) { + if (LIKELY(false || (data[6].qvalue <= 144))) { + result[0] += -1.16853189584804; + } else { + result[0] += -76.18237147015647; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += -565.1080728858356; + } else { + result[0] += 55.856898322571; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 428))) { + result[0] += -22.36169324873774; + } else { + result[0] += -588.8674501865916; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 444))) { + result[0] += 45.149287996873234; + } else { + result[0] += 472.9437979483076; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 110))) { + if (UNLIKELY(false || (data[0].qvalue <= 426))) { + result[0] += -14.394012939959836; + } else { + result[0] += -486.3581999710685; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 58))) { + result[0] += -223.70340084114315; + } else { + if (UNLIKELY(false || (data[10].qvalue <= 88))) { + if (LIKELY(false || (data[0].qvalue <= 400))) { + result[0] += -62.55465904994526; + } else { + result[0] += 711.6569177288845; + } + } else { + result[0] += -22.12709356395005; + } + } + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 176))) { + if (UNLIKELY(false || (data[6].qvalue <= 136))) { + if (LIKELY(false || (data[4].qvalue <= 108))) { + if (UNLIKELY(false || (data[9].qvalue <= 36))) { + result[0] += 353.08425684031425; + } else { + if (UNLIKELY(false || (data[8].qvalue <= 70))) { + result[0] += -488.1757777871809; + } else { + result[0] += 63.64288315962378; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 470))) { + if (LIKELY(false || (data[1].qvalue <= 164))) { + result[0] += -1340.3345402421837; + } else { + result[0] += 611.7688119589316; + } + } else { + result[0] += -5.555477568010339; + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 124))) { + if (LIKELY(false || (data[2].qvalue <= 216))) { + if (UNLIKELY(false || (data[10].qvalue <= 48))) { + result[0] += 101.80136762073124; + } else { + result[0] += 394.0454932921462; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + result[0] += -54.51295272481636; + } else { + result[0] += 369.30662351586926; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 464))) { + if (LIKELY(false || (data[2].qvalue <= 168))) { + result[0] += -280.5682849399643; + } else { + result[0] += 363.49964354819656; + } + } else { + if (LIKELY(false || (data[2].qvalue <= 94))) { + result[0] += 287.00161641580763; + } else { + result[0] += -27.192437609622456; + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (UNLIKELY(false || (data[4].qvalue <= 102))) { + result[0] += 4.886466438190986; + } else { + result[0] += -273.6325616457949; + } + } else { + result[0] += 83.09343404390128; + } + } + } + if (LIKELY(false || (data[0].qvalue <= 466))) { + if (LIKELY(false || (data[6].qvalue <= 168))) { + if (LIKELY(false || (data[0].qvalue <= 426))) { + if (UNLIKELY(false || (data[9].qvalue <= 42))) { + if (LIKELY(false || (data[9].qvalue <= 28))) { + if (LIKELY(false || (data[9].qvalue <= 26))) { + result[0] += -12.741637583507675; + } else { + result[0] += 237.9588004111412; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 22))) { + result[0] += -668.1312059622767; + } else { + result[0] += -74.96557902347062; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 96))) { + if (LIKELY(false || (data[1].qvalue <= 92))) { + result[0] += 1.2337343498563946; + } else { + result[0] += -70.38800250372651; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 98))) { + result[0] += 57.91224145436725; + } else { + result[0] += -291.61695102438495; + } + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 46))) { + if (LIKELY(false || (data[3].qvalue <= 138))) { + if (LIKELY(false || (data[2].qvalue <= 78))) { + result[0] += -16.243170204026153; + } else { + result[0] += 316.91551138966975; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 122))) { + result[0] += -653.2681828545267; + } else { + result[0] += -140.38689952692195; + } + } + } else { + if (LIKELY(false || (data[10].qvalue <= 116))) { + if (UNLIKELY(false || (data[9].qvalue <= 76))) { + result[0] += 227.09410027903155; + } else { + result[0] += 41.580049580383246; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 112))) { + result[0] += 421.02462529251864; + } else { + result[0] += -29.85412788966988; + } + } + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 84))) { + if (LIKELY(false || (data[0].qvalue <= 462))) { + if (UNLIKELY(false || (data[0].qvalue <= 430))) { + result[0] += -40.88170364268475; + } else { + if (LIKELY(false || (data[0].qvalue <= 458))) { + result[0] += -438.26722679955304; + } else { + result[0] += -153.8759027518579; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 170))) { + result[0] += 596.3699073028564; + } else { + result[0] += -65.1138629905028; + } + } + } else { + if (LIKELY(false || (data[2].qvalue <= 170))) { + if (LIKELY(false || (data[0].qvalue <= 450))) { + if (UNLIKELY(false || (data[8].qvalue <= 96))) { + result[0] += -174.18902213573074; + } else { + result[0] += 92.92329912293484; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 140))) { + result[0] += 1029.8844129302536; + } else { + result[0] += 173.42569967619715; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 398))) { + result[0] += 117.61714884981923; + } else { + result[0] += -165.11066227116845; + } + } + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 132))) { + if (UNLIKELY(false || (data[3].qvalue <= 140))) { + if (LIKELY(false || (data[8].qvalue <= 152))) { + result[0] += 275.79848862506384; + } else { + result[0] += -208.23065829261407; + } + } else { + result[0] += 44.68615918739647; + } + } else { + result[0] += -104.44556106745557; + } + } + if (LIKELY(false || (data[0].qvalue <= 452))) { + if (LIKELY(false || (data[7].qvalue <= 178))) { + if (LIKELY(false || (data[2].qvalue <= 212))) { + if (LIKELY(false || (data[7].qvalue <= 166))) { + if (LIKELY(false || (data[6].qvalue <= 168))) { + if (LIKELY(false || (data[6].qvalue <= 166))) { + result[0] += 0.17111650034346793; + } else { + result[0] += 170.19199658576656; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 114))) { + result[0] += -7.762253214369399; + } else { + result[0] += -229.65839448385577; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 154))) { + if (UNLIKELY(false || (data[7].qvalue <= 168))) { + result[0] += -1527.4651782070064; + } else { + result[0] += -32.341146195408825; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 400))) { + result[0] += 53.78926807880971; + } else { + result[0] += 365.35095312342645; + } + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 40))) { + result[0] += -195.8247595287494; + } else { + if (LIKELY(false || (data[0].qvalue <= 446))) { + result[0] += -6.730419002246698; + } else { + result[0] += -485.7584640261381; + } + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 68))) { + result[0] += -302.41592403705386; + } else { + result[0] += -41.37635294838609; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 146))) { + if (UNLIKELY(false || (data[8].qvalue <= 68))) { + if (LIKELY(false || (data[9].qvalue <= 52))) { + if (LIKELY(false || (data[4].qvalue <= 108))) { + if (LIKELY(false || (data[0].qvalue <= 462))) { + result[0] += 105.6696751827489; + } else { + result[0] += 422.3152713156167; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 154))) { + result[0] += -338.9649649852936; + } else { + result[0] += 94.81444748000506; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 472))) { + if (UNLIKELY(false || (data[2].qvalue <= 32))) { + result[0] += 307.59982556152346; + } else { + result[0] += -2059.814399773849; + } + } else { + result[0] += -114.64047998985878; + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 120))) { + if (LIKELY(false || (data[0].qvalue <= 468))) { + if (UNLIKELY(false || (data[6].qvalue <= 56))) { + result[0] += -105.53208465517486; + } else { + result[0] += 242.79092160164484; + } + } else { + result[0] += 794.8344802547236; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 462))) { + result[0] += 211.67574313790456; + } else { + result[0] += -1052.0226856718868; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 470))) { + if (LIKELY(false || (data[2].qvalue <= 220))) { + if (LIKELY(false || (data[5].qvalue <= 122))) { + if (UNLIKELY(false || (data[8].qvalue <= 14))) { + result[0] += 607.7257898100032; + } else { + result[0] += 31.625663758552975; + } + } else { + result[0] += -165.26478050781773; + } + } else { + result[0] += -212.34245839321358; + } + } else { + if (LIKELY(false || (data[7].qvalue <= 188))) { + result[0] += 195.00720541235728; + } else { + result[0] += -16.708118864398536; + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 442))) { + if (LIKELY(false || (data[1].qvalue <= 148))) { + if (LIKELY(false || (data[8].qvalue <= 154))) { + if (UNLIKELY(false || (data[0].qvalue <= 54))) { + result[0] += -25.89138246104899; + } else { + if (LIKELY(false || (data[5].qvalue <= 84))) { + if (LIKELY(false || (data[8].qvalue <= 116))) { + result[0] += 6.395755334851975; + } else { + result[0] += -41.8874025902268; + } + } else { + if (LIKELY(false || (data[10].qvalue <= 136))) { + result[0] += 43.17739670120483; + } else { + result[0] += -113.04299411317503; + } + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 342))) { + result[0] += -9.406748281219956; + } else { + result[0] += -151.8391684094208; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 102))) { + if (LIKELY(false || (data[0].qvalue <= 308))) { + result[0] += 68.15272951691435; + } else { + result[0] += 653.7243028200384; + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 118))) { + if (UNLIKELY(false || (data[0].qvalue <= 360))) { + result[0] += 4.132956299238849; + } else { + if (LIKELY(false || (data[2].qvalue <= 140))) { + result[0] += -391.93226074781654; + } else { + result[0] += -64.14020916590836; + } + } + } else { + result[0] += -42.79033951970415; + } + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 140))) { + if (UNLIKELY(false || (data[3].qvalue <= 16))) { + result[0] += 673.2372143809691; + } else { + if (LIKELY(false || (data[8].qvalue <= 72))) { + if (UNLIKELY(false || (data[0].qvalue <= 456))) { + if (LIKELY(false || (data[2].qvalue <= 68))) { + result[0] += 72.78571585943284; + } else { + result[0] += -372.52419624016306; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 84))) { + result[0] += -470.8106385006224; + } else { + result[0] += 129.07388124253447; + } + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 448))) { + if (UNLIKELY(false || (data[3].qvalue <= 106))) { + result[0] += -167.83421517433203; + } else { + result[0] += 120.89570583762936; + } + } else { + if (LIKELY(false || (data[1].qvalue <= 144))) { + result[0] += 166.50278950598; + } else { + result[0] += 479.51545503859353; + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + if (LIKELY(false || (data[2].qvalue <= 216))) { + if (LIKELY(false || (data[8].qvalue <= 102))) { + if (LIKELY(false || (data[4].qvalue <= 136))) { + result[0] += -85.88265694904688; + } else { + result[0] += 158.08464637061354; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 160))) { + result[0] += 566.823827422359; + } else { + result[0] += 36.90095759323719; + } + } + } else { + result[0] += -221.08459607201044; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 162))) { + result[0] += 279.3051543277019; + } else { + if (UNLIKELY(false || (data[4].qvalue <= 88))) { + result[0] += 225.3999401051908; + } else { + if (LIKELY(false || (data[0].qvalue <= 472))) { + result[0] += -146.03870534454174; + } else { + result[0] += 135.266458595812; + } + } + } + } + } + } + if (UNLIKELY(false || (data[7].qvalue <= 10))) { + if (LIKELY(false || (data[3].qvalue <= 54))) { + if (LIKELY(false || (data[8].qvalue <= 56))) { + if (LIKELY(false || (data[5].qvalue <= 30))) { + if (UNLIKELY(false || (data[4].qvalue <= 2))) { + result[0] += 95.33481345166494; + } else { + if (UNLIKELY(false || (data[10].qvalue <= 36))) { + result[0] += -51.475241127929905; + } else { + result[0] += 45.54996053721217; + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 18))) { + result[0] += 193.47951816282492; + } else { + result[0] += 41.14647330634432; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 8))) { + result[0] += -256.5011877316934; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 44))) { + result[0] += -78.031723920287; + } else { + if (UNLIKELY(false || (data[10].qvalue <= 58))) { + result[0] += -70.18547029244044; + } else { + result[0] += 58.299178728803355; + } + } + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 56))) { + result[0] += 377.13982577866017; + } else { + result[0] += 125.3708679348752; + } + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 22))) { + if (LIKELY(false || (data[6].qvalue <= 20))) { + if (LIKELY(false || (data[3].qvalue <= 48))) { + if (LIKELY(false || (data[5].qvalue <= 38))) { + if (LIKELY(false || (data[2].qvalue <= 122))) { + result[0] += -27.262907617303707; + } else { + result[0] += -296.1225155241601; + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 94))) { + result[0] += -744.6855172554128; + } else { + result[0] += -295.5306383117119; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 74))) { + result[0] += 131.65876610363532; + } else { + result[0] += 275.9221477857152; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 54))) { + result[0] += -266.18327484029714; + } else { + result[0] += -500.5391151498484; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 14))) { + if (LIKELY(false || (data[4].qvalue <= 34))) { + if (LIKELY(false || (data[8].qvalue <= 86))) { + result[0] += -10.58236111787111; + } else { + if (UNLIKELY(false || (data[5].qvalue <= 8))) { + result[0] += -39.932805444944165; + } else { + result[0] += 159.2150451080038; + } + } + } else { + result[0] += 204.44418412478637; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 30))) { + if (LIKELY(false || (data[8].qvalue <= 12))) { + if (UNLIKELY(false || (data[1].qvalue <= 62))) { + result[0] += 125.99859295088723; + } else { + result[0] += -10.15551076665495; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 20))) { + result[0] += -190.22917265383091; + } else { + result[0] += -33.04391029665733; + } + } + } else { + if (LIKELY(false || (data[10].qvalue <= 98))) { + if (UNLIKELY(false || (data[7].qvalue <= 32))) { + result[0] += 63.784678651859224; + } else { + result[0] += 4.687841489245975; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 70))) { + result[0] += -193.67925649583466; + } else { + result[0] += -11.97318656822869; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 410))) { + if (UNLIKELY(false || (data[9].qvalue <= 54))) { + if (UNLIKELY(false || (data[4].qvalue <= 74))) { + if (LIKELY(false || (data[0].qvalue <= 298))) { + result[0] += -37.83989902430271; + } else { + result[0] += -246.41304263463073; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 36))) { + if (UNLIKELY(false || (data[0].qvalue <= 196))) { + result[0] += -24.698954353394658; + } else { + result[0] += 269.8421976574775; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 82))) { + if (LIKELY(false || (data[4].qvalue <= 128))) { + result[0] += 121.42816960444512; + } else { + result[0] += -204.12026096629276; + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 86))) { + result[0] += -150.30284718707338; + } else { + result[0] += -9.526517783514938; + } + } + } + } + } else { + result[0] += 0.6374046148668437; + } + } else { + if (UNLIKELY(false || (data[9].qvalue <= 10))) { + if (LIKELY(false || (data[0].qvalue <= 456))) { + if (UNLIKELY(false || (data[3].qvalue <= 160))) { + if (UNLIKELY(false || (data[2].qvalue <= 56))) { + result[0] += -844.9505630005908; + } else { + result[0] += -266.4489226017028; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 448))) { + result[0] += -59.61765022395716; + } else { + if (UNLIKELY(false || (data[9].qvalue <= 0))) { + result[0] += -422.1597338477869; + } else { + result[0] += 307.5185329018873; + } + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 84))) { + result[0] += 259.874337152467; + } else { + result[0] += -0.14677676347402133; + } + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 76))) { + if (LIKELY(false || (data[2].qvalue <= 60))) { + if (UNLIKELY(false || (data[4].qvalue <= 82))) { + if (LIKELY(false || (data[6].qvalue <= 66))) { + result[0] += 6.901818537027612; + } else { + result[0] += 346.73948874314306; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 0))) { + result[0] += 767.3329148187853; + } else { + result[0] += -55.71428668797978; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 462))) { + if (UNLIKELY(false || (data[8].qvalue <= 62))) { + result[0] += -1186.4827177702393; + } else { + result[0] += -248.10230638115846; + } + } else { + result[0] += 317.2999956644618; + } + } + } else { + if (LIKELY(false || (data[8].qvalue <= 128))) { + if (UNLIKELY(false || (data[10].qvalue <= 46))) { + if (UNLIKELY(false || (data[8].qvalue <= 6))) { + result[0] += 424.73738367278276; + } else { + result[0] += -69.29711136275134; + } + } else { + if (LIKELY(false || (data[9].qvalue <= 38))) { + result[0] += 93.41031827005968; + } else { + result[0] += 256.43713558484785; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 86))) { + if (LIKELY(false || (data[0].qvalue <= 456))) { + result[0] += -194.80499023272242; + } else { + result[0] += 84.16720477646794; + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 130))) { + result[0] += 1117.9205291606104; + } else { + result[0] += 32.52858858984742; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 442))) { + if (LIKELY(false || (data[6].qvalue <= 146))) { + if (LIKELY(false || (data[0].qvalue <= 360))) { + if (UNLIKELY(false || (data[4].qvalue <= 38))) { + if (UNLIKELY(false || (data[6].qvalue <= 24))) { + if (UNLIKELY(false || (data[9].qvalue <= 94))) { + result[0] += -509.2763123794946; + } else { + result[0] += -10.28309254566162; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 46))) { + result[0] += 65.60802974294101; + } else { + result[0] += -11.172650598142237; + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 40))) { + if (UNLIKELY(false || (data[0].qvalue <= 170))) { + result[0] += -379.1246497217576; + } else { + result[0] += -880.7154812437998; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 38))) { + result[0] += 33.59073760216112; + } else { + result[0] += -19.2301464816471; + } + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 198))) { + if (LIKELY(false || (data[2].qvalue <= 186))) { + if (LIKELY(false || (data[3].qvalue <= 144))) { + result[0] += 33.69989668265042; + } else { + result[0] += 208.11777026466976; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 126))) { + result[0] += -4.6241213678677076; + } else { + result[0] += -258.29323587381907; + } + } + } else { + result[0] += -240.03769405637382; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 94))) { + if (UNLIKELY(false || (data[4].qvalue <= 20))) { + if (LIKELY(false || (data[0].qvalue <= 400))) { + result[0] += -1.3019828319392528; + } else { + result[0] += 595.7005340010636; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 258))) { + result[0] += -9.190887637624767; + } else { + result[0] += -285.64672594943085; + } + } + } else { + if (LIKELY(false || (data[7].qvalue <= 154))) { + if (UNLIKELY(false || (data[0].qvalue <= 344))) { + result[0] += 40.545254733724846; + } else { + if (UNLIKELY(false || (data[0].qvalue <= 416))) { + result[0] += -233.93172162375063; + } else { + result[0] += -22.286785557997515; + } + } + } else { + result[0] += 70.10413972244889; + } + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 140))) { + if (LIKELY(false || (data[4].qvalue <= 114))) { + if (LIKELY(false || (data[4].qvalue <= 112))) { + if (UNLIKELY(false || (data[3].qvalue <= 16))) { + result[0] += 634.2191665019159; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 78))) { + result[0] += 6.665393953447264; + } else { + result[0] += 149.39801863389712; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 154))) { + result[0] += 1226.4539406622023; + } else { + result[0] += 294.9277796445255; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 158))) { + result[0] += 235.1520118587282; + } else { + result[0] += -166.61985769647302; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + if (LIKELY(false || (data[2].qvalue <= 216))) { + result[0] += 5.169623713259927; + } else { + result[0] += -199.41738547469845; + } + } else { + result[0] += 71.9476904143754; + } + } + } + if (UNLIKELY(false || (data[0].qvalue <= 48))) { + if (UNLIKELY(false || (data[0].qvalue <= 0))) { + result[0] += -78.00407393572347; + } else { + if (LIKELY(false || (data[2].qvalue <= 202))) { + if (LIKELY(false || (data[3].qvalue <= 104))) { + if (LIKELY(false || (data[4].qvalue <= 90))) { + if (UNLIKELY(false || (data[2].qvalue <= 0))) { + result[0] += -70.6888999910638; + } else { + result[0] += -10.627670242516414; + } + } else { + if (LIKELY(false || (data[10].qvalue <= 66))) { + result[0] += -107.69047239009149; + } else { + result[0] += -13.414907874287316; + } + } + } else { + if (LIKELY(false || (data[1].qvalue <= 138))) { + if (UNLIKELY(false || (data[10].qvalue <= 12))) { + result[0] += 69.98431682634853; + } else { + result[0] += -59.43734140381587; + } + } else { + result[0] += 78.30655993653257; + } + } + } else { + result[0] += 82.01495795861331; + } + } + } else { + if (UNLIKELY(false || (data[4].qvalue <= 0))) { + if (LIKELY(false || (data[2].qvalue <= 128))) { + if (LIKELY(false || (data[0].qvalue <= 226))) { + if (LIKELY(false || (data[2].qvalue <= 58))) { + if (UNLIKELY(false || (data[2].qvalue <= 24))) { + result[0] += 150.52913267303913; + } else { + result[0] += 6.758849638068968; + } + } else { + result[0] += 219.10434569977826; + } + } else { + result[0] += 274.1316917335302; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 332))) { + result[0] += -84.8367008241603; + } else { + result[0] += 317.87177416169243; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 14))) { + if (LIKELY(false || (data[0].qvalue <= 406))) { + if (LIKELY(false || (data[4].qvalue <= 28))) { + if (UNLIKELY(false || (data[9].qvalue <= 108))) { + result[0] += 152.21239710120096; + } else { + result[0] += -49.218505477191925; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 30))) { + result[0] += -369.09166571523195; + } else { + result[0] += -59.62264580017301; + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 122))) { + if (UNLIKELY(false || (data[2].qvalue <= 72))) { + result[0] += 225.48342290509692; + } else { + result[0] += 454.8914585206501; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 420))) { + result[0] += -316.42278833229403; + } else { + result[0] += 89.88278332332641; + } + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 22))) { + if (LIKELY(false || (data[3].qvalue <= 18))) { + if (LIKELY(false || (data[4].qvalue <= 26))) { + result[0] += 83.70108736546864; + } else { + result[0] += -47.03238163468578; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 172))) { + result[0] += 64.00534235112683; + } else { + result[0] += 241.06317309770884; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 28))) { + if (UNLIKELY(false || (data[9].qvalue <= 108))) { + result[0] += 41.85255742051009; + } else { + result[0] += -122.59387579963109; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 12))) { + result[0] += 72.23509636314206; + } else { + result[0] += 2.3792085384878088; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 460))) { + if (LIKELY(false || (data[6].qvalue <= 168))) { + if (LIKELY(false || (data[0].qvalue <= 432))) { + if (LIKELY(false || (data[1].qvalue <= 124))) { + if (LIKELY(false || (data[5].qvalue <= 62))) { + if (LIKELY(false || (data[8].qvalue <= 110))) { + result[0] += -1.818322290568549; + } else { + result[0] += -86.51820960810343; + } + } else { + if (UNLIKELY(false || (data[6].qvalue <= 48))) { + result[0] += 122.64593189161003; + } else { + result[0] += 8.90299476140962; + } + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 64))) { + if (LIKELY(false || (data[8].qvalue <= 64))) { + result[0] += 27.811889151480592; + } else { + result[0] += 564.1322969672179; + } + } else { + if (UNLIKELY(false || (data[10].qvalue <= 66))) { + result[0] += -961.542648801726; + } else { + result[0] += -67.5677271302586; + } + } + } + } else { + if (LIKELY(false || (data[10].qvalue <= 148))) { + if (UNLIKELY(false || (data[1].qvalue <= 0))) { + result[0] += -271.4318221979432; + } else { + if (UNLIKELY(false || (data[5].qvalue <= 16))) { + result[0] += 688.4114417873475; + } else { + result[0] += 58.42035899984279; + } + } + } else { + result[0] += -307.4296972195184; + } + } + } else { + if (UNLIKELY(false || (data[8].qvalue <= 78))) { + if (UNLIKELY(false || (data[0].qvalue <= 434))) { + result[0] += -47.84229037947061; + } else { + result[0] += -341.83457990151675; + } + } else { + result[0] += -24.69473744066359; + } + } + } else { + if (LIKELY(false || (data[6].qvalue <= 176))) { + if (UNLIKELY(false || (data[6].qvalue <= 156))) { + if (LIKELY(false || (data[4].qvalue <= 108))) { + if (LIKELY(false || (data[1].qvalue <= 90))) { + if (LIKELY(false || (data[0].qvalue <= 468))) { + result[0] += -237.2028222385908; + } else { + result[0] += 235.23343926289132; + } + } else { + result[0] += 275.68252452796696; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 470))) { + if (UNLIKELY(false || (data[9].qvalue <= 6))) { + result[0] += 276.655894681045; + } else { + result[0] += -1138.9810647848005; + } + } else { + result[0] += -24.815816631740525; + } + } + } else { + if (LIKELY(false || (data[4].qvalue <= 124))) { + if (UNLIKELY(false || (data[8].qvalue <= 94))) { + result[0] += 468.636846801675; + } else { + result[0] += 173.66365185212408; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 464))) { + result[0] += -156.75158265385107; + } else { + if (UNLIKELY(false || (data[5].qvalue <= 72))) { + result[0] += 418.66415661473707; + } else { + result[0] += 16.60197418193807; + } + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 472))) { + if (UNLIKELY(false || (data[4].qvalue <= 102))) { + if (LIKELY(false || (data[3].qvalue <= 178))) { + result[0] += 112.29216412676556; + } else { + result[0] += -589.6749782826543; + } + } else { + if (LIKELY(false || (data[6].qvalue <= 186))) { + result[0] += -126.14743791646418; + } else { + result[0] += -697.0503549298137; + } + } + } else { + result[0] += 130.01903028897132; + } + } + } + if (LIKELY(false || (data[0].qvalue <= 250))) { + if (LIKELY(false || (data[2].qvalue <= 96))) { + if (LIKELY(false || (data[10].qvalue <= 110))) { + result[0] += -15.209203725866741; + } else { + result[0] += -151.01702601859964; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 116))) { + result[0] += 33.169088504059346; + } else { + if (UNLIKELY(false || (data[2].qvalue <= 136))) { + if (LIKELY(false || (data[2].qvalue <= 134))) { + if (LIKELY(false || (data[9].qvalue <= 132))) { + result[0] += -0.5460734226255192; + } else { + result[0] += -166.18365097405575; + } + } else { + result[0] += -630.4456930959705; + } + } else { + result[0] += 9.610897218995794; + } + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 12))) { + if (UNLIKELY(false || (data[5].qvalue <= 4))) { + if (UNLIKELY(false || (data[7].qvalue <= 4))) { + result[0] += 292.5131264640451; + } else { + if (LIKELY(false || (data[0].qvalue <= 346))) { + if (LIKELY(false || (data[2].qvalue <= 46))) { + result[0] += -90.96452643248735; + } else { + result[0] += -554.2646443755457; + } + } else { + result[0] += 78.62976969599482; + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 316))) { + if (LIKELY(false || (data[3].qvalue <= 54))) { + if (UNLIKELY(false || (data[9].qvalue <= 102))) { + result[0] += 178.17593860823325; + } else { + result[0] += 25.683590998151445; + } + } else { + result[0] += 478.77786108066925; + } + } else { + result[0] += 181.52015147560186; + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 122))) { + if (UNLIKELY(false || (data[6].qvalue <= 54))) { + if (UNLIKELY(false || (data[3].qvalue <= 10))) { + if (LIKELY(false || (data[4].qvalue <= 46))) { + result[0] += 392.13693925483733; + } else { + result[0] += 93.50921667351855; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 26))) { + result[0] += -171.2455308856315; + } else { + result[0] += 65.00191321740634; + } + } + } else { + if (UNLIKELY(false || (data[5].qvalue <= 54))) { + if (LIKELY(false || (data[3].qvalue <= 60))) { + result[0] += -29.670526192638437; + } else { + result[0] += -250.61118646403057; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 72))) { + result[0] += 139.50112391138077; + } else { + result[0] += 5.183830796614544; + } + } + } + } else { + if (LIKELY(false || (data[0].qvalue <= 410))) { + if (UNLIKELY(false || (data[6].qvalue <= 6))) { + if (LIKELY(false || (data[0].qvalue <= 376))) { + result[0] += 72.4511482444548; + } else { + result[0] += 480.9499826432033; + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 50))) { + result[0] += -339.82229606896135; + } else { + result[0] += -34.16011839278788; + } + } + } else { + if (UNLIKELY(false || (data[1].qvalue <= 52))) { + if (LIKELY(false || (data[4].qvalue <= 22))) { + result[0] += 110.97363027456811; + } else { + result[0] += 409.03280051924963; + } + } else { + if (LIKELY(false || (data[0].qvalue <= 468))) { + result[0] += -25.656918184151955; + } else { + result[0] += 673.645954177365; + } + } + } + } + } + } + if (LIKELY(false || (data[0].qvalue <= 338))) { + if (LIKELY(false || (data[2].qvalue <= 96))) { + if (LIKELY(false || (data[10].qvalue <= 110))) { + if (LIKELY(false || (data[2].qvalue <= 86))) { + if (LIKELY(false || (data[2].qvalue <= 82))) { + if (LIKELY(false || (data[10].qvalue <= 74))) { + result[0] += -0.803059421195404; + } else { + result[0] += -41.251005891223926; + } + } else { + result[0] += 143.4559149834235; + } + } else { + if (LIKELY(false || (data[3].qvalue <= 116))) { + if (UNLIKELY(false || (data[7].qvalue <= 26))) { + result[0] += 2.624252580548391; + } else { + result[0] += -227.38952669158417; + } + } else { + result[0] += 45.837014796557646; + } + } + } else { + result[0] += -195.38067959274719; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 116))) { + if (LIKELY(false || (data[0].qvalue <= 176))) { + result[0] += 12.646781663778135; + } else { + if (UNLIKELY(false || (data[8].qvalue <= 50))) { + result[0] += 217.37905842024804; + } else { + if (LIKELY(false || (data[9].qvalue <= 100))) { + result[0] += 18.490879448389602; + } else { + result[0] += 157.2895805126459; + } + } + } + } else { + if (LIKELY(false || (data[5].qvalue <= 88))) { + if (LIKELY(false || (data[10].qvalue <= 130))) { + if (LIKELY(false || (data[10].qvalue <= 124))) { + result[0] += -25.376404193377994; + } else { + result[0] += -234.0117708294; + } + } else { + result[0] += 44.70662468521388; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 130))) { + if (UNLIKELY(false || (data[0].qvalue <= 146))) { + result[0] += 12.86322888112687; + } else { + result[0] += 200.2312601015275; + } + } else { + if (UNLIKELY(false || (data[2].qvalue <= 162))) { + result[0] += -49.82927755893318; + } else { + result[0] += 34.59858546002059; + } + } + } + } + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 64))) { + if (LIKELY(false || (data[7].qvalue <= 60))) { + result[0] += 44.53611652626999; + } else { + if (UNLIKELY(false || (data[3].qvalue <= 100))) { + result[0] += 378.92912612636616; + } else { + result[0] += 114.52994706164273; + } + } + } else { + if (UNLIKELY(false || (data[3].qvalue <= 12))) { + if (UNLIKELY(false || (data[0].qvalue <= 416))) { + if (LIKELY(false || (data[10].qvalue <= 92))) { + result[0] += -628.9086683084666; + } else { + result[0] += 107.65691728510829; + } + } else { + if (UNLIKELY(false || (data[7].qvalue <= 148))) { + if (LIKELY(false || (data[0].qvalue <= 432))) { + result[0] += 30.295081044275346; + } else { + result[0] += 409.9041492892673; + } + } else { + result[0] += -143.73134957553725; + } + } + } else { + if (LIKELY(false || (data[9].qvalue <= 146))) { + if (LIKELY(false || (data[9].qvalue <= 138))) { + if (UNLIKELY(false || (data[6].qvalue <= 54))) { + result[0] += 149.66087572300066; + } else { + result[0] += -2.415057229240939; + } + } else { + if (UNLIKELY(false || (data[0].qvalue <= 418))) { + result[0] += -377.3930807574788; + } else { + result[0] += 56.193799271376434; + } + } + } else { + result[0] += 545.2850103362496; + } + } + } + } + + // Apply base_scores + result[0] += 0; + + // Apply postprocessor + if (!pred_margin) { postprocess(result); } +} + +void fj_predictor::postprocess(double* result) +{ + // Do nothing +} + +// Feature names array +const char* fj_predictor::feature_names[fj_predictor::NUM_FEATURES] = {"time", + "initial_violation_count", + "max_nnz_per_row", + "n_binary_vars", + "n_constraints", + "n_integer_vars", + "n_variables", + "nnz", + "nnz_stddev", + "sparsity", + "unbalancedness", + "uses_load_balancing"}; diff --git a/cpp/src/utilities/models/fj_predictor/quantize.cpp b/cpp/src/utilities/models/fj_predictor/quantize.cpp new file mode 100644 index 0000000000..4bd50efafc --- /dev/null +++ b/cpp/src/utilities/models/fj_predictor/quantize.cpp @@ -0,0 +1,1180 @@ + +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "header.h" + +static const double threshold[] = { + 0.054247050000000012, + 0.077229550000000022, + 0.099138400000000002, + 0.12088200000000002, + 0.14051650000000002, + 0.15852850000000004, + 0.17679700000000004, + 0.19352650000000002, + 0.21021800000000002, + 0.22558550000000002, + 0.24112700000000004, + 0.25703600000000004, + 0.27504900000000004, + 0.29268150000000009, + 0.31128750000000005, + 0.33090700000000001, + 0.34973950000000004, + 0.36837650000000005, + 0.38755100000000003, + 0.40692750000000005, + 0.42464700000000005, + 0.44212900000000005, + 0.46115050000000007, + 0.47875750000000006, + 0.49512500000000004, + 0.51183000000000012, + 0.52816850000000015, + 0.54488850000000011, + 0.56229750000000012, + 0.57881900000000008, + 0.59550200000000009, + 0.61214950000000012, + 0.62885000000000013, + 0.64696700000000018, + 0.66350950000000009, + 0.67997150000000006, + 0.69847550000000014, + 0.7159675000000002, + 0.73463250000000013, + 0.7525940000000001, + 0.77200500000000016, + 0.79034150000000014, + 0.80839950000000005, + 0.82770850000000007, + 0.8455950000000001, + 0.86400500000000013, + 0.88264050000000005, + 0.9004295000000001, + 0.91857200000000006, + 0.93668050000000014, + 0.95404050000000018, + 0.97391950000000016, + 0.99200750000000004, + 1.0099000000000002, + 1.0289150000000002, + 1.0465600000000002, + 1.0646550000000004, + 1.08342, + 1.1014450000000002, + 1.1397000000000002, + 1.1587800000000004, + 1.1770050000000001, + 1.1970650000000003, + 1.2149600000000003, + 1.2334100000000003, + 1.2509550000000003, + 1.2698400000000001, + 1.2892250000000003, + 1.3068250000000001, + 1.3257700000000001, + 1.3439900000000002, + 1.3627650000000002, + 1.3818950000000003, + 1.3997500000000003, + 1.4181450000000002, + 1.4367350000000003, + 1.4547850000000002, + 1.472415, + 1.4914700000000003, + 1.5101100000000003, + 1.5280200000000004, + 1.5462100000000001, + 1.5645300000000002, + 1.5825350000000002, + 1.6018750000000004, + 1.6398350000000002, + 1.6586050000000003, + 1.6957200000000003, + 1.7142950000000001, + 1.7329100000000002, + 1.7519050000000003, + 1.7889300000000004, + 1.8084400000000003, + 1.84535, + 1.8819950000000003, + 1.9005400000000001, + 1.9191650000000002, + 1.9559750000000002, + 1.9729050000000001, + 1.9908450000000002, + 2.0093400000000003, + 2.0269500000000007, + 2.0634850000000005, + 2.0812900000000005, + 2.0995600000000008, + 2.1174250000000003, + 2.1351400000000003, + 2.1524100000000006, + 2.1703750000000004, + 2.1880300000000004, + 2.2057200000000008, + 2.2244050000000004, + 2.2413600000000007, + 2.2589850000000005, + 2.2762350000000002, + 2.2941550000000004, + 2.3126850000000005, + 2.3300800000000002, + 2.3486300000000004, + 2.3668800000000005, + 2.3849800000000001, + 2.4039000000000006, + 2.4207550000000002, + 2.4380750000000004, + 2.4563950000000006, + 2.4738150000000005, + 2.4917700000000003, + 2.5087150000000005, + 2.5256750000000001, + 2.541865, + 2.5750850000000001, + 2.5926700000000005, + 2.6103800000000006, + 2.6462700000000008, + 2.6646600000000005, + 2.6821750000000004, + 2.6995250000000008, + 2.7161250000000003, + 2.7339100000000003, + 2.7519550000000002, + 2.7684900000000003, + 2.7861950000000006, + 2.8210050000000004, + 2.8391800000000003, + 2.8555100000000002, + 2.8724000000000003, + 2.8905950000000007, + 2.9086600000000007, + 2.9273950000000002, + 2.9445150000000004, + 2.9631150000000006, + 2.9813800000000006, + 3.0161400000000005, + 3.0351250000000003, + 3.0535200000000002, + 3.0715450000000004, + 3.0891000000000006, + 3.1077850000000002, + 3.1259600000000005, + 3.1445250000000002, + 3.1638600000000001, + 3.2029950000000005, + 3.2231000000000005, + 3.2427850000000005, + 3.2622000000000004, + 3.2821500000000001, + 3.3022150000000008, + 3.3210900000000003, + 3.3419400000000006, + 3.3625650000000005, + 3.3821600000000003, + 3.4255600000000004, + 3.4476550000000006, + 3.4694350000000003, + 3.4931250000000005, + 3.5172500000000002, + 3.5405750000000005, + 3.5633350000000004, + 3.5881150000000006, + 3.6123700000000007, + 3.6388050000000005, + 3.6645250000000007, + 3.6929500000000002, + 3.7206100000000002, + 3.7487850000000003, + 3.8103150000000006, + 3.8410400000000005, + 3.8753900000000003, + 3.9096100000000003, + 3.9434900000000002, + 3.9779100000000001, + 4.0157400000000001, + 4.0538750000000006, + 4.0947550000000001, + 4.134875000000001, + 4.1789850000000008, + 4.2277500000000012, + 4.2804200000000003, + 4.3315450000000011, + 4.3857550000000005, + 4.4510850000000008, + 4.5179800000000006, + 4.5945550000000006, + 4.6744550000000009, + 4.7680650000000009, + 4.8691100000000009, + 4.9748750000000008, + 5.1040400000000004, + 5.2357750000000012, + 5.3817050000000011, + 5.5487400000000013, + 5.7226800000000013, + 5.9013650000000011, + 6.0933350000000006, + 6.2959750000000012, + 6.5019050000000016, + 6.7376150000000008, + 6.9850100000000008, + 7.2452850000000009, + 7.5387250000000003, + 7.8497500000000011, + 8.206685000000002, + 8.5881250000000016, + 9.0114350000000005, + 9.5016150000000028, + 10.006050000000002, + 10.534950000000002, + 11.213450000000003, + 12.138300000000003, + 13.343550000000002, + 14.829750000000002, + 16.520350000000004, + 19.149450000000005, + 22.676800000000004, + 27.456400000000002, + 34.351750000000003, + 47.268800000000006, + 1.0000000180025095e-35, + 1.5000000000000002, + 2.5000000000000004, + 3.5000000000000004, + 4.5000000000000009, + 7.5000000000000009, + 9.5000000000000018, + 13.500000000000002, + 20.500000000000004, + 27.500000000000004, + 32.500000000000007, + 35.500000000000007, + 73.500000000000014, + 145.50000000000003, + 210.50000000000003, + 256.50000000000006, + 267.50000000000006, + 271.50000000000006, + 338.50000000000006, + 416.50000000000006, + 627.50000000000011, + 646.50000000000011, + 654.50000000000011, + 728.50000000000011, + 785.50000000000011, + 917.50000000000011, + 943.50000000000011, + 1105.5000000000002, + 1168.5000000000002, + 1414.5000000000002, + 1511.5000000000002, + 1766.5000000000002, + 2142.5000000000005, + 2424.5000000000005, + 2476.5000000000005, + 2824.5000000000005, + 3182.5000000000005, + 3233.5000000000005, + 3334.5000000000005, + 3428.5000000000005, + 3734.5000000000005, + 3862.0000000000005, + 4068.5000000000005, + 4221.5000000000009, + 4296.5000000000009, + 4701.5000000000009, + 4781.5000000000009, + 6990.5000000000009, + 7213.5000000000009, + 7449.5000000000009, + 8540.5000000000018, + 9118.5000000000018, + 9521.5000000000018, + 10539.500000000002, + 11717.500000000002, + 12095.500000000002, + 13029.500000000002, + 14400.000000000002, + 14615.500000000002, + 15096.500000000002, + 15817.500000000002, + 16830.500000000004, + 17346.500000000004, + 20007.000000000004, + 20988.500000000004, + 21532.500000000004, + 23313.500000000004, + 24562.500000000004, + 30402.000000000004, + 35983.500000000007, + 40040.000000000007, + 41987.500000000007, + 43540.000000000007, + 44215.500000000007, + 45601.500000000007, + 47232.500000000007, + 54221.000000000007, + 68471.500000000015, + 74712.500000000015, + 89913.000000000015, + 125519.00000000001, + 149049.00000000003, + 230573.00000000003, + 3.5000000000000004, + 4.5000000000000009, + 5.5000000000000009, + 6.5000000000000009, + 7.5000000000000009, + 8.5000000000000018, + 11.500000000000002, + 13.500000000000002, + 14.500000000000002, + 15.500000000000002, + 17.500000000000004, + 24.500000000000004, + 29.500000000000004, + 30.500000000000004, + 31.500000000000004, + 33.500000000000007, + 35.500000000000007, + 37.500000000000007, + 40.500000000000007, + 41.500000000000007, + 42.500000000000007, + 43.500000000000007, + 45.500000000000007, + 49.500000000000007, + 50.500000000000007, + 55.500000000000007, + 59.500000000000007, + 60.500000000000007, + 65.500000000000014, + 68.000000000000014, + 71.000000000000014, + 72.500000000000014, + 73.500000000000014, + 74.500000000000014, + 80.500000000000014, + 90.000000000000014, + 96.500000000000014, + 98.500000000000014, + 99.500000000000014, + 105.50000000000001, + 108.50000000000001, + 112.50000000000001, + 113.50000000000001, + 116.50000000000001, + 117.50000000000001, + 118.50000000000001, + 121.50000000000001, + 124.50000000000001, + 127.00000000000001, + 156.50000000000003, + 158.50000000000003, + 174.50000000000003, + 182.00000000000003, + 186.50000000000003, + 190.50000000000003, + 193.00000000000003, + 197.50000000000003, + 215.50000000000003, + 230.50000000000003, + 238.50000000000003, + 242.50000000000003, + 274.50000000000006, + 287.50000000000006, + 310.00000000000006, + 317.00000000000006, + 475.50000000000006, + 478.00000000000006, + 501.50000000000006, + 508.00000000000006, + 558.00000000000011, + 586.50000000000011, + 594.50000000000011, + 644.50000000000011, + 665.50000000000011, + 686.00000000000011, + 829.00000000000011, + 933.00000000000011, + 973.00000000000011, + 1000.5000000000001, + 1023.0000000000001, + 1101.5000000000002, + 1594.0000000000002, + 1773.5000000000002, + 1868.0000000000002, + 1935.0000000000002, + 2075.5000000000005, + 2658.5000000000005, + 2842.0000000000005, + 2928.0000000000005, + 3159.0000000000005, + 3844.0000000000005, + 3959.0000000000005, + 5956.0000000000009, + 6744.5000000000009, + 7399.5000000000009, + 8990.0000000000018, + 9523.5000000000018, + 11378.500000000002, + 12174.500000000002, + 15191.500000000002, + 18400.500000000004, + 20392.500000000004, + 21043.500000000004, + 21811.000000000004, + 30397.000000000004, + 30926.500000000004, + 33002.500000000007, + 48554.500000000007, + 56727.500000000007, + 77009.500000000015, + 97457.000000000015, + 117653.00000000001, + 137723.50000000003, + 145615.50000000003, + 1.0000000180025095e-35, + 19.000000000000004, + 31.000000000000004, + 55.500000000000007, + 69.500000000000014, + 70.500000000000014, + 99.000000000000014, + 119.00000000000001, + 176.50000000000003, + 189.50000000000003, + 196.00000000000003, + 201.00000000000003, + 219.00000000000003, + 263.00000000000006, + 290.00000000000006, + 306.00000000000006, + 317.50000000000006, + 361.50000000000006, + 384.00000000000006, + 417.00000000000006, + 448.50000000000006, + 534.50000000000011, + 593.50000000000011, + 622.50000000000011, + 693.50000000000011, + 889.50000000000011, + 927.50000000000011, + 1116.5000000000002, + 1455.5000000000002, + 1498.0000000000002, + 1995.5000000000002, + 2027.5000000000002, + 2105.5000000000005, + 2463.5000000000005, + 2518.5000000000005, + 2595.5000000000005, + 2630.0000000000005, + 3336.0000000000005, + 4222.5000000000009, + 4319.5000000000009, + 4543.5000000000009, + 5196.5000000000009, + 5211.0000000000009, + 5279.0000000000009, + 5647.0000000000009, + 6426.5000000000009, + 6471.5000000000009, + 6781.5000000000009, + 7008.0000000000009, + 7273.0000000000009, + 8219.0000000000018, + 8313.0000000000018, + 8346.0000000000018, + 8890.5000000000018, + 9579.5000000000018, + 10096.000000000002, + 10296.500000000002, + 10412.000000000002, + 10570.000000000002, + 10722.500000000002, + 11284.000000000002, + 11482.000000000002, + 12403.500000000002, + 13743.000000000002, + 16623.500000000004, + 17719.000000000004, + 20045.500000000004, + 22896.000000000004, + 24893.000000000004, + 29297.500000000004, + 32360.500000000004, + 32826.000000000007, + 42917.000000000007, + 45655.500000000007, + 49866.500000000007, + 55546.000000000007, + 57005.000000000007, + 60339.000000000007, + 70968.500000000015, + 77454.500000000015, + 86012.000000000015, + 86932.000000000015, + 88579.000000000015, + 98965.000000000015, + 127333.50000000001, + 169734.00000000003, + 233398.00000000003, + 320960.50000000006, + 341815.50000000006, + 1315285.0000000002, + 2747995.0000000005, + 10.500000000000002, + 16.500000000000004, + 38.000000000000007, + 42.500000000000007, + 61.500000000000007, + 76.500000000000014, + 101.50000000000001, + 300.50000000000006, + 464.00000000000006, + 578.50000000000011, + 640.00000000000011, + 653.50000000000011, + 721.00000000000011, + 849.00000000000011, + 1140.0000000000002, + 1197.0000000000002, + 1359.0000000000002, + 1627.5000000000002, + 1925.0000000000002, + 2298.5000000000005, + 2356.0000000000005, + 2394.5000000000005, + 2529.0000000000005, + 3626.5000000000005, + 4099.0000000000009, + 5157.0000000000009, + 5521.5000000000009, + 7489.0000000000009, + 8797.5000000000018, + 9813.0000000000018, + 11172.500000000002, + 11823.000000000002, + 12490.500000000002, + 12827.000000000002, + 13934.000000000002, + 14718.000000000002, + 14944.500000000002, + 15924.500000000002, + 16641.500000000004, + 17124.000000000004, + 17409.000000000004, + 17805.000000000004, + 18541.500000000004, + 20890.500000000004, + 22641.000000000004, + 23763.500000000004, + 27226.000000000004, + 28849.000000000004, + 34014.000000000007, + 39933.000000000007, + 48730.000000000007, + 55362.000000000007, + 58384.000000000007, + 91637.500000000015, + 97665.000000000015, + 108046.50000000001, + 111482.00000000001, + 127740.00000000001, + 141620.50000000003, + 146171.50000000003, + 164305.50000000003, + 167857.50000000003, + 206722.00000000003, + 215085.50000000003, + 271255.50000000006, + 364916.50000000006, + 376899.50000000006, + 446870.00000000006, + 504658.00000000006, + 534428.50000000012, + 2917540.0000000005, + 8.0000000000000018, + 40.500000000000007, + 69.500000000000014, + 76.000000000000014, + 99.000000000000014, + 102.50000000000001, + 116.50000000000001, + 128.50000000000003, + 147.50000000000003, + 161.00000000000003, + 190.50000000000003, + 201.00000000000003, + 218.00000000000003, + 246.00000000000003, + 301.50000000000006, + 306.50000000000006, + 411.00000000000006, + 448.50000000000006, + 490.50000000000006, + 739.00000000000011, + 786.50000000000011, + 835.00000000000011, + 1435.5000000000002, + 1490.0000000000002, + 1585.0000000000002, + 1947.5000000000002, + 2089.5000000000005, + 2208.0000000000005, + 2262.0000000000005, + 2504.0000000000005, + 2595.5000000000005, + 2694.0000000000005, + 3290.5000000000005, + 3486.0000000000005, + 5199.5000000000009, + 5448.5000000000009, + 7246.0000000000009, + 8305.0000000000018, + 8345.5000000000018, + 8884.0000000000018, + 11484.500000000002, + 12542.000000000002, + 13993.500000000002, + 14321.000000000002, + 14486.500000000002, + 29297.500000000004, + 41026.500000000007, + 45655.500000000007, + 57005.000000000007, + 62415.500000000007, + 69861.000000000015, + 77483.500000000015, + 82860.500000000015, + 86012.000000000015, + 105975.50000000001, + 122661.50000000001, + 135210.50000000003, + 154808.00000000003, + 165244.00000000003, + 195544.50000000003, + 321530.50000000006, + 341815.50000000006, + 14.500000000000002, + 66.500000000000014, + 242.50000000000003, + 265.50000000000006, + 306.50000000000006, + 341.50000000000006, + 518.00000000000011, + 730.50000000000011, + 758.50000000000011, + 896.50000000000011, + 982.50000000000011, + 1007.5000000000001, + 1108.0000000000002, + 1133.5000000000002, + 1178.0000000000002, + 1436.0000000000002, + 1599.5000000000002, + 1916.5000000000002, + 2036.0000000000002, + 2066.5000000000005, + 2456.0000000000005, + 2816.0000000000005, + 2849.0000000000005, + 3190.5000000000005, + 3226.5000000000005, + 3564.0000000000005, + 3726.5000000000005, + 4004.5000000000005, + 4719.5000000000009, + 5175.0000000000009, + 5663.0000000000009, + 5848.0000000000009, + 7347.5000000000009, + 8946.0000000000018, + 10887.500000000002, + 11141.500000000002, + 12386.000000000002, + 12893.000000000002, + 12981.000000000002, + 13236.000000000002, + 13742.500000000002, + 13874.000000000002, + 14396.000000000002, + 14769.500000000002, + 16616.000000000004, + 17772.500000000004, + 18503.000000000004, + 18928.000000000004, + 19963.500000000004, + 22431.000000000004, + 22656.000000000004, + 23089.500000000004, + 25009.500000000004, + 25158.500000000004, + 29801.500000000004, + 30930.500000000004, + 32941.500000000007, + 33925.000000000007, + 35517.000000000007, + 35966.500000000007, + 37351.500000000007, + 38399.000000000007, + 40769.000000000007, + 42352.500000000007, + 43749.000000000007, + 45226.000000000007, + 46621.500000000007, + 57435.500000000007, + 58829.500000000007, + 60692.000000000007, + 62587.500000000007, + 64024.000000000007, + 67166.000000000015, + 72662.000000000015, + 73712.500000000015, + 74556.500000000015, + 78614.500000000015, + 83046.500000000015, + 98968.000000000015, + 102883.00000000001, + 124003.00000000001, + 131103.50000000003, + 135356.50000000003, + 145271.00000000003, + 167192.00000000003, + 172253.00000000003, + 198236.00000000003, + 209702.50000000003, + 298248.50000000006, + 333792.50000000006, + 343878.50000000006, + 404776.00000000006, + 460381.00000000006, + 628991.00000000012, + 83.500000000000014, + 183.50000000000003, + 468.00000000000006, + 1396.0000000000002, + 2433.0000000000005, + 3465.0000000000005, + 3645.5000000000005, + 5270.0000000000009, + 6121.5000000000009, + 6510.5000000000009, + 7307.0000000000009, + 8358.5000000000018, + 8618.5000000000018, + 8828.5000000000018, + 9135.0000000000018, + 13322.500000000002, + 14156.000000000002, + 14773.000000000002, + 18059.000000000004, + 19199.000000000004, + 20030.000000000004, + 21288.500000000004, + 22830.500000000004, + 39487.500000000007, + 51294.500000000007, + 55184.000000000007, + 58766.500000000007, + 78090.000000000015, + 79203.500000000015, + 85545.500000000015, + 86497.500000000015, + 96575.500000000015, + 101879.50000000001, + 104890.50000000001, + 106509.00000000001, + 111473.00000000001, + 133804.50000000003, + 143238.00000000003, + 152266.50000000003, + 188714.00000000003, + 193144.50000000003, + 201859.00000000003, + 204145.00000000003, + 211177.00000000003, + 216282.00000000003, + 227905.50000000003, + 233158.50000000003, + 242732.00000000003, + 247534.50000000003, + 252735.00000000003, + 263001.50000000006, + 267680.00000000006, + 286053.50000000006, + 301277.50000000006, + 316160.00000000006, + 326485.50000000006, + 341223.00000000006, + 382538.50000000006, + 397214.50000000006, + 420753.50000000006, + 427014.00000000006, + 474030.00000000006, + 484374.50000000006, + 490157.50000000006, + 493561.50000000006, + 517275.50000000006, + 525159.50000000012, + 536338.50000000012, + 555454.50000000012, + 577752.00000000012, + 616908.00000000012, + 643671.50000000012, + 665158.00000000012, + 688029.50000000012, + 748092.00000000012, + 791728.50000000012, + 805917.00000000012, + 833331.00000000012, + 846131.50000000012, + 889587.00000000012, + 925745.00000000012, + 960960.50000000012, + 993471.50000000012, + 1000860.0000000001, + 1033940.0000000001, + 1088030.0000000002, + 1104190.0000000002, + 1141090.0000000002, + 1227215.0000000002, + 1302410.0000000002, + 1327790.0000000002, + 1565560.0000000002, + 1713120.0000000002, + 1986515.0000000002, + 2295955.0000000005, + 2459445.0000000005, + 2786970.0000000005, + 4311770.0000000009, + 4897840.0000000009, + 7675510.0000000009, + 12103450.000000002, + 1.0000000180025095e-35, + 0.025444000000000005, + 0.079041650000000005, + 0.47263850000000002, + 0.51316300000000015, + 0.61466950000000009, + 0.70002100000000012, + 0.84333400000000014, + 1.0452850000000002, + 1.2015150000000003, + 1.4797750000000003, + 1.8327900000000001, + 2.2462950000000004, + 2.4300000000000006, + 2.5927000000000002, + 2.6670150000000006, + 2.8069300000000004, + 3.1766550000000007, + 3.2475100000000006, + 3.3747950000000002, + 3.4595750000000005, + 3.6730550000000002, + 3.8561550000000007, + 4.4209650000000007, + 4.6392950000000015, + 4.865940000000001, + 4.9636350000000009, + 5.1358750000000013, + 5.5377350000000005, + 5.745820000000001, + 5.9460500000000005, + 6.3433350000000006, + 6.3778100000000011, + 6.5389250000000008, + 6.6173200000000012, + 7.1363800000000017, + 7.4741750000000016, + 7.6210850000000008, + 8.6734450000000027, + 9.4143250000000016, + 11.746350000000001, + 12.052000000000001, + 12.250050000000002, + 13.267300000000001, + 17.041250000000002, + 17.986650000000001, + 21.122200000000003, + 21.844500000000004, + 23.696150000000006, + 25.750650000000004, + 26.183450000000004, + 27.539350000000002, + 31.875650000000004, + 36.846500000000006, + 38.188850000000009, + 44.081200000000003, + 52.333200000000005, + 54.716150000000006, + 60.769450000000006, + 65.705850000000012, + 85.315750000000023, + 108.21300000000001, + 122.06700000000002, + 150.02500000000001, + 157.58100000000002, + 167.36850000000001, + 180.69350000000003, + 206.27400000000003, + 282.04500000000002, + 301.06900000000002, + 431.21800000000002, + 513.42400000000009, + 780.49650000000008, + 813.8660000000001, + 850.00950000000012, + 1134.5900000000004, + 1346.6950000000002, + 2370.7800000000002, + 2928.5150000000008, + 1.3466250000000002e-05, + 2.2664350000000006e-05, + 2.9555800000000003e-05, + 3.5335850000000012e-05, + 3.6073500000000006e-05, + 3.9458500000000002e-05, + 4.3825250000000005e-05, + 5.8516e-05, + 6.4233050000000024e-05, + 7.0834150000000017e-05, + 8.9559350000000012e-05, + 9.5430800000000015e-05, + 0.00011455550000000001, + 0.00012689600000000001, + 0.00014240450000000004, + 0.00016513700000000001, + 0.00018171050000000003, + 0.00018756350000000003, + 0.00020882050000000003, + 0.00021563800000000001, + 0.00022961900000000003, + 0.00026418050000000004, + 0.00027278350000000008, + 0.00029956750000000005, + 0.00032692350000000002, + 0.00033880400000000006, + 0.00035918650000000006, + 0.00038193300000000004, + 0.00042447950000000008, + 0.00043747650000000002, + 0.00044906850000000007, + 0.00049152850000000004, + 0.00052591450000000011, + 0.00057647100000000017, + 0.00058207650000000012, + 0.00098674500000000003, + 0.0011041950000000003, + 0.0012247250000000001, + 0.0012767100000000001, + 0.0013355900000000002, + 0.001509605, + 0.0017419350000000002, + 0.0018100400000000004, + 0.0018508650000000004, + 0.0021851550000000007, + 0.0024035350000000005, + 0.00336228, + 0.0038408950000000004, + 0.004200225000000001, + 0.0047036750000000009, + 0.0051918100000000007, + 0.0055236550000000014, + 0.0057092000000000002, + 0.0059764100000000006, + 0.0069381250000000007, + 0.0073699550000000009, + 0.007565500000000001, + 0.0084957950000000022, + 0.0089344600000000017, + 0.0099261550000000007, + 0.014203600000000002, + 0.021387700000000006, + 0.023916750000000004, + 0.026089700000000004, + 0.05008255000000001, + 0.055832150000000004, + 0.060545100000000004, + 0.070805450000000006, + 0.087852150000000004, + 0.11160100000000002, + 0.12906450000000003, + 0.18728800000000004, + 0.22001300000000004, + 0.25453400000000004, + 0.26550900000000005, + 0.31291300000000005, + 0.35104150000000006, + 0.41306200000000004, + 0.57042700000000013, + 0.64485350000000008, + 0.96875000000000011, + 1.0000000180025095e-35, + 0.0039979100000000012, + 0.0082457700000000012, + 0.038986650000000005, + 0.054666050000000001, + 0.070373600000000022, + 0.097335900000000017, + 0.19564850000000003, + 0.20630450000000003, + 0.24236000000000002, + 0.25417800000000007, + 0.25770150000000008, + 0.27507600000000004, + 0.28204950000000006, + 0.28742100000000009, + 0.29918200000000006, + 0.30476150000000007, + 0.33851400000000004, + 0.37035050000000008, + 0.38581450000000006, + 0.44627100000000003, + 0.45825600000000005, + 0.4758035000000001, + 0.52170800000000017, + 0.54062450000000017, + 0.54514950000000006, + 0.57222350000000011, + 0.67992200000000003, + 0.70028650000000014, + 0.75992800000000005, + 0.81040200000000018, + 0.85826700000000011, + 0.95305950000000006, + 0.98670650000000015, + 1.0208650000000001, + 1.0782450000000001, + 1.1067700000000003, + 1.1631450000000003, + 1.2699600000000004, + 1.2872250000000001, + 1.2987750000000002, + 1.3993100000000003, + 1.5141650000000002, + 1.5734200000000003, + 1.6142300000000003, + 1.6834550000000001, + 1.8165200000000001, + 1.9725750000000002, + 2.0513250000000007, + 2.0544200000000008, + 2.1066100000000003, + 2.2829500000000005, + 2.3961550000000003, + 2.6080850000000004, + 2.7410500000000004, + 3.0611950000000001, + 3.5134100000000008, + 3.5836800000000006, + 4.7010550000000011, + 4.7679900000000011, + 5.106815000000001, + 5.1937250000000015, + 5.2231500000000013, + 5.349705000000001, + 5.4792200000000006, + 5.6649900000000004, + 5.8800200000000009, + 6.6231050000000007, + 6.9449300000000003, + 7.3326800000000008, + 8.199600000000002, + 11.794000000000002, + 16.289250000000003, + 19.559350000000006, + 24.766300000000005, + 1.0000000180025095e-35, +}; + +static const int th_begin[] = { + 0, + 237, + 320, + 434, + 525, + 596, + 658, + 752, + 853, + 932, + 1013, + 1088, +}; + +static const int th_len[] = { + 237, + 83, + 114, + 91, + 71, + 62, + 94, + 101, + 79, + 81, + 75, + 1, +}; + +/* + * \brief Function to convert a feature value into bin index. + * \param val Feature value, in floating-point + * \param fid Feature identifier + * \return bin Index corresponding to given feature value + */ +int fj_predictor::quantize(double val, unsigned fid) +{ + const size_t offset = th_begin[fid]; + const double* array = &threshold[offset]; + int len = th_len[fid]; + int low = 0; + int high = len; + int mid; + double mval; + // It is possible th_begin[i] == [total_num_threshold]. This means that + // all features i, (i+1), ... are not used for any of the splits in the model. + // So in this case, just return something + if (offset == 1089 || val < array[0]) { return -10; } + while (low + 1 < high) { + mid = (low + high) / 2; + mval = array[mid]; + if (val == mval) { + return mid * 2; + } else if (val < mval) { + high = mid; + } else { + low = mid; + } + } + if (array[low] == val) { + return low * 2; + } else if (high == len) { + return len * 2; + } else { + return low * 2 + 1; + } +} diff --git a/cpp/src/utilities/seed_generator.cu b/cpp/src/utilities/seed_generator.cu index 1da6662bc1..612093a7a8 100644 --- a/cpp/src/utilities/seed_generator.cu +++ b/cpp/src/utilities/seed_generator.cu @@ -1,10 +1,11 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ #include -int64_t cuopt::seed_generator::seed_ = 0; +int64_t cuopt::seed_generator::base_seed_ = 0; +std::atomic cuopt::seed_generator::epoch_{0}; diff --git a/cpp/src/utilities/seed_generator.cuh b/cpp/src/utilities/seed_generator.cuh index dd5e79d847..888936eb79 100644 --- a/cpp/src/utilities/seed_generator.cuh +++ b/cpp/src/utilities/seed_generator.cuh @@ -1,29 +1,50 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ #pragma once +#include #include #include namespace cuopt { -// TODO: should be thread local? class seed_generator { - static int64_t seed_; + static int64_t base_seed_; + // Monotonically increasing epoch; incremented on every set_seed() call. + // Thread-local state compares against this to detect resets, even when + // the same seed value is set again (e.g., repeated solve_mip() calls). + static std::atomic epoch_; + + struct thread_state_t { + int64_t counter{0}; + int64_t last_epoch{-1}; + }; + + static thread_state_t& local_state() + { + thread_local thread_state_t state; + int64_t current_epoch = epoch_.load(std::memory_order_acquire); + if (state.last_epoch != current_epoch) { + state.counter = base_seed_; + state.last_epoch = current_epoch; + } + return state; + } public: template static void set_seed(seed_t seed) { #ifdef BENCHMARK - seed_ = std::random_device{}(); + base_seed_ = std::random_device{}(); #else - seed_ = static_cast(seed); + base_seed_ = static_cast(seed); #endif + epoch_.fetch_add(1, std::memory_order_release); } template static void set_seed(arg0 seed0, arg1 seed1, args... seeds) @@ -31,7 +52,19 @@ class seed_generator { set_seed(seed1 + ((seed0 + seed1) * (seed0 + seed1 + 1) / 2), seeds...); } - static int64_t get_seed() { return seed_++; } +#if SEED_GENERATOR_DEBUG + static int64_t get_seed(const char* caller = __builtin_FUNCTION(), + const char* file = __builtin_FILE(), + int line = __builtin_LINE()) + { + printf("&&&&&&& SEED CALLED BY %s:%d: %s() ***\n", file, line, caller); + return local_state().counter++; + } +#else + static int64_t get_seed() { return local_state().counter++; } +#endif + + static int64_t peek_seed() { return local_state().counter; } public: seed_generator(seed_generator const&) = delete; diff --git a/cpp/src/utilities/termination_checker.hpp b/cpp/src/utilities/termination_checker.hpp new file mode 100644 index 0000000000..180b194608 --- /dev/null +++ b/cpp/src/utilities/termination_checker.hpp @@ -0,0 +1,219 @@ +/* clang-format off */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* clang-format on */ + +#pragma once + +#include +#include +#include + +#include + +#include "timer.hpp" +#include "work_limit_context.hpp" + +namespace cuopt { + +/** + * Unified termination checker that subsumes timer_t and work_limit_timer_t. + * + * In non-deterministic mode: checks wall-clock time. + * In deterministic mode: checks work units via work_limit_context_t. + * In BOTH modes: checks parent chain (inheriting root wall-clock limit) and user callbacks. + * + * This is the single timer type used throughout the solver. It replaces work_limit_timer_t. + */ +class termination_checker_t { + public: + struct root_tag_t {}; + + // Root constructor (top-level solver, wall-clock only) + explicit termination_checker_t(double time_limit, root_tag_t) + : deterministic(false), + work_limit(time_limit), + timer(time_limit), + work_context(nullptr), + work_units_at_start(0), + parent_(nullptr) + { + } + + // Non-deterministic constructor with parent + termination_checker_t(double time_limit_, const termination_checker_t& parent) + : deterministic(false), + work_limit(time_limit_), + timer(time_limit_), + work_context(nullptr), + work_units_at_start(0), + parent_(&parent) + { + } + + // Deterministic constructor with parent (inherits parent's termination) + termination_checker_t(work_limit_context_t& context, + double work_limit_, + const termination_checker_t& parent) + : deterministic(context.deterministic), + work_limit(work_limit_), + timer(work_limit_), + work_context(&context), + work_units_at_start(context.deterministic ? context.current_work() : 0), + parent_(&parent) + { + } + + void set_parent(const termination_checker_t* parent) { parent_ = parent; } + const termination_checker_t* get_parent() const { return parent_; } + + void set_termination_callback(bool (*cb)(void*), void* data) + { + termination_callback_ = cb; + termination_callback_data_ = data; + } + + bool check(const char* caller = __builtin_FUNCTION(), + const char* file = __builtin_FILE(), + int line = __builtin_LINE()) const noexcept + { + if (termination_callback_ != nullptr && termination_callback_(termination_callback_data_)) { + return true; + } + + if (parent_ != nullptr && parent_->check()) { return true; } + + if (deterministic) { + if (!work_context) { return false; } + double elapsed_since_start = work_context->current_work() - work_units_at_start; + bool finished_now = elapsed_since_start >= work_limit; + if (finished_now && !finished) { + finished = true; + double actual_elapsed_time = timer.elapsed_time(); + + if (work_limit > 0 && std::abs(actual_elapsed_time - work_limit) / work_limit > 0.10) { + CUOPT_LOG_ERROR( + "%s:%d: %s(): Work limit timer finished with a large discrepancy: %fs for %fwu " + "(global: %g, start: %g)", + file, + line, + caller, + actual_elapsed_time, + work_limit, + work_context->current_work(), + work_units_at_start); + } + } + return finished; + } else { + return timer.check_time_limit(); + } + } + + // Aliases for compatibility with work_limit_timer_t and timer_t interfaces + bool check_time_limit(const char* caller = __builtin_FUNCTION(), + const char* file = __builtin_FILE(), + int line = __builtin_LINE()) const noexcept + { + return check(caller, file, line); + } + + bool check_limit(const char* caller = __builtin_FUNCTION(), + const char* file = __builtin_FILE(), + int line = __builtin_LINE()) const noexcept + { + return check(caller, file, line); + } + + void record_work(double work_units, + const char* caller = __builtin_FUNCTION(), + const char* file = __builtin_FILE(), + int line = __builtin_LINE()) + { + if (deterministic && work_context) { + // debugging info + double parent_elapsed_time = parent_ != nullptr ? parent_->timer.elapsed_time() : 0.0; + double parent_time_limit = parent_ != nullptr ? parent_->timer.get_time_limit() : 0.0; + + CUOPT_LOG_DEBUG("%s:%d: %s(): Recorded %f work units in %fs, total %f (parent time: %g/%g)", + file, + line, + caller, + work_units, + timer.elapsed_time(), + work_context->current_work(), + parent_elapsed_time, + parent_time_limit); + work_context->record_work_sync_on_horizon(work_units); + } + } + + double remaining_units() const noexcept + { + if (deterministic) { + if (!work_context) { return work_limit; } + double elapsed_since_start = work_context->current_work() - work_units_at_start; + return std::max(0.0, work_limit - elapsed_since_start); + } else { + return timer.remaining_time(); + } + } + + double remaining_time() const noexcept { return remaining_units(); } + + double elapsed_time() const noexcept + { + if (deterministic) { + if (!work_context) { return 0.0; } + return work_context->current_work() - work_units_at_start; + } else { + return timer.elapsed_time(); + } + } + + bool check_half_time() const noexcept + { + if (deterministic) { + if (!work_context) { return false; } + double elapsed_since_start = work_context->current_work() - work_units_at_start; + return elapsed_since_start >= work_limit / 2; + } else { + return timer.check_half_time(); + } + } + + double clamp_remaining_time(double desired_time) const noexcept + { + return std::min(desired_time, remaining_time()); + } + + double get_time_limit() const noexcept + { + if (deterministic) { + return work_limit; + } else { + return timer.get_time_limit(); + } + } + + double get_tic_start() const noexcept { return timer.get_tic_start(); } + + timer_t timer; + double work_limit{}; + mutable bool finished{false}; + bool deterministic{false}; + work_limit_context_t* work_context{nullptr}; + double work_units_at_start{0}; + + private: + const termination_checker_t* parent_{nullptr}; + bool (*termination_callback_)(void*) = nullptr; + void* termination_callback_data_ = nullptr; +}; + +// Backward compatibility +using work_limit_timer_t = termination_checker_t; + +} // namespace cuopt diff --git a/cpp/src/utilities/timer.hpp b/cpp/src/utilities/timer.hpp index b7ab6a63bd..ccfab4c57f 100644 --- a/cpp/src/utilities/timer.hpp +++ b/cpp/src/utilities/timer.hpp @@ -34,7 +34,21 @@ class timer_t { elapsed_time()); } - bool check_time_limit() const noexcept { return elapsed_time() >= time_limit; } + bool check_time_limit(const char* caller = __builtin_FUNCTION(), + const char* file = __builtin_FILE(), + int line = __builtin_LINE()) const noexcept + { + bool elapsed = elapsed_time() >= time_limit; + // if (elapsed) { + // printf("************ TIME LIMIT (%.2gs) REACHED BY %s:%d: %s() ***\n", + // time_limit, + // file, + // line, + // caller); + // //__builtin_trap(); + // } + return elapsed; + } bool check_half_time() const noexcept { return elapsed_time() >= time_limit / 2; } diff --git a/cpp/src/utilities/version_info.cpp b/cpp/src/utilities/version_info.cpp index ec9db5130b..bfcb02ce16 100644 --- a/cpp/src/utilities/version_info.cpp +++ b/cpp/src/utilities/version_info.cpp @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -163,6 +163,44 @@ static double get_available_memory_gb() return kb / (1024.0 * 1024.0); // Convert KB to GB } +double get_cpu_max_clock_mhz() +{ + // Cache the result - CPU max clock doesn't change during execution + // thread_local to avoid an unecessary sync inserted by the compiler + // due to the standard mandating thread-safe static local variable initialization + // the extra work here is minimal. + thread_local static double cached_mhz = []() { + // Try sysfs cpufreq interface first (returns frequency in KHz) + // FIXME: assumes all available CPUs have the same max clock as CPU0 + std::ifstream freq_file("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"); + if (freq_file.is_open()) { + long khz = 0; + freq_file >> khz; + if (khz > 0) { return khz / 1e3; } + } + + // Fallback: parse /proc/cpuinfo for "cpu MHz" + std::ifstream cpuinfo("/proc/cpuinfo"); + if (!cpuinfo.is_open()) return 0.0; + + std::string line; + double max_mhz = 0.0; + while (std::getline(cpuinfo, line)) { + if (line.find("cpu MHz") != std::string::npos) { + std::size_t colon = line.find(':'); + if (colon != std::string::npos) { + double mhz = std::stod(line.substr(colon + 1)); + if (mhz > max_mhz) { max_mhz = mhz; } + } + } + } + + return max_mhz; + }(); + + return cached_mhz; +} + void print_version_info() { int device_id = 0; diff --git a/cpp/src/utilities/version_info.hpp b/cpp/src/utilities/version_info.hpp index dbadea8ecd..ea909e7c19 100644 --- a/cpp/src/utilities/version_info.hpp +++ b/cpp/src/utilities/version_info.hpp @@ -8,4 +8,5 @@ namespace cuopt { void print_version_info(); -} +double get_cpu_max_clock_mhz(); +} // namespace cuopt diff --git a/cpp/src/utilities/work_limit_context.hpp b/cpp/src/utilities/work_limit_context.hpp index c75a37b818..0c8435c77a 100644 --- a/cpp/src/utilities/work_limit_context.hpp +++ b/cpp/src/utilities/work_limit_context.hpp @@ -17,30 +17,155 @@ #pragma once #include +#include +#include +#include +#include +#include #include #include +#include +#include +#include "producer_sync.hpp" #include "timer.hpp" #include "work_unit_scheduler.hpp" namespace cuopt { +inline double read_work_unit_scale_env_or_default(const char* env_name, double default_value) +{ + const char* env_value = std::getenv(env_name); + if (env_value == nullptr || env_value[0] == '\0') { return default_value; } + + errno = 0; + char* end_ptr = nullptr; + const double parsed_value = std::strtod(env_value, &end_ptr); + const bool valid_value = errno == 0 && end_ptr != env_value && *end_ptr == '\0' && + std::isfinite(parsed_value) && parsed_value > 0.0; + cuopt_assert(valid_value, "Invalid work-unit scale env var"); + return parsed_value; +} + struct work_limit_context_t { double global_work_units_elapsed{0.0}; double total_sync_time{0.0}; // Total time spent waiting at sync barriers (seconds) bool deterministic{false}; work_unit_scheduler_t* scheduler{nullptr}; + producer_sync_t* producer_sync{nullptr}; std::string name; + std::unique_ptr> producer_work_units_elapsed{ + std::make_unique>(0.0)}; + double producer_progress_scale{ + read_work_unit_scale_env_or_default("CUOPT_GPU_HEUR_WORK_UNIT_SCALE", 1.0)}; + double work_unit_scale{1.0}; work_limit_context_t(const std::string& name) : name(name) {} + work_limit_context_t(const work_limit_context_t& other) + : global_work_units_elapsed(other.global_work_units_elapsed), + total_sync_time(other.total_sync_time), + deterministic(other.deterministic), + scheduler(other.scheduler), + producer_sync(other.producer_sync), + name(other.name), + producer_work_units_elapsed(std::make_unique>( + other.producer_work_units_elapsed->load(std::memory_order_acquire))), + producer_progress_scale(other.producer_progress_scale), + work_unit_scale(other.work_unit_scale) + { + } + + work_limit_context_t(work_limit_context_t&& other) noexcept + : global_work_units_elapsed(other.global_work_units_elapsed), + total_sync_time(other.total_sync_time), + deterministic(other.deterministic), + scheduler(other.scheduler), + producer_sync(other.producer_sync), + name(std::move(other.name)), + producer_work_units_elapsed(std::make_unique>( + other.producer_work_units_elapsed->load(std::memory_order_acquire))), + producer_progress_scale(other.producer_progress_scale), + work_unit_scale(other.work_unit_scale) + { + } + + work_limit_context_t& operator=(const work_limit_context_t& other) + { + if (this == &other) { return *this; } + global_work_units_elapsed = other.global_work_units_elapsed; + total_sync_time = other.total_sync_time; + deterministic = other.deterministic; + scheduler = other.scheduler; + producer_sync = other.producer_sync; + name = other.name; + producer_work_units_elapsed = std::make_unique>( + other.producer_work_units_elapsed->load(std::memory_order_acquire)); + producer_progress_scale = other.producer_progress_scale; + work_unit_scale = other.work_unit_scale; + return *this; + } + + work_limit_context_t& operator=(work_limit_context_t&& other) noexcept + { + if (this == &other) { return *this; } + global_work_units_elapsed = other.global_work_units_elapsed; + total_sync_time = other.total_sync_time; + deterministic = other.deterministic; + scheduler = other.scheduler; + producer_sync = other.producer_sync; + name = std::move(other.name); + producer_work_units_elapsed = std::make_unique>( + other.producer_work_units_elapsed->load(std::memory_order_acquire)); + producer_progress_scale = other.producer_progress_scale; + work_unit_scale = other.work_unit_scale; + return *this; + } + + double current_work() const noexcept { return global_work_units_elapsed; } + + double current_producer_work() const noexcept { return current_work() * producer_progress_scale; } + + std::atomic* producer_progress_ptr() noexcept + { + return producer_work_units_elapsed.get(); + } + + void attach_producer_sync(producer_sync_t* producer_sync_) + { + producer_sync = producer_sync_; + producer_work_units_elapsed->store(current_producer_work(), std::memory_order_release); + if (work_unit_scale != 1.0) { + CUOPT_DETERMINISM_LOG("[%s] Using work-unit scale %f", name.c_str(), work_unit_scale); + } + } + + void detach_producer_sync() noexcept { producer_sync = nullptr; } + + void set_current_work(double total_work, bool notify_producer = true) + { + if (!deterministic) return; + cuopt_assert(total_work + 1e-12 >= global_work_units_elapsed, + "Deterministic work progress must be monotonic"); + global_work_units_elapsed = total_work; + producer_work_units_elapsed->store(current_producer_work(), std::memory_order_release); + if (notify_producer && producer_sync != nullptr) { producer_sync->notify_progress(); } + } + void record_work_sync_on_horizon(double work) { if (!deterministic) return; - global_work_units_elapsed += work; - if (scheduler) { scheduler->on_work_recorded(*this, global_work_units_elapsed); } + cuopt_assert(std::isfinite(work), "Recorded work must be finite"); + cuopt_assert(work >= 0.0, "Recorded work must be non-negative"); + const double scaled_work = work * work_unit_scale; + const double total_work = global_work_units_elapsed + scaled_work; + set_current_work(total_work, false); + if (scheduler) { scheduler->on_work_recorded(*this, total_work); } + if (producer_sync != nullptr) { producer_sync->notify_progress(); } } + + void record_work(double work) { record_work_sync_on_horizon(work); } }; } // namespace cuopt diff --git a/cpp/src/utilities/work_limit_timer.hpp b/cpp/src/utilities/work_limit_timer.hpp new file mode 100644 index 0000000000..801a3e5ee9 --- /dev/null +++ b/cpp/src/utilities/work_limit_timer.hpp @@ -0,0 +1,11 @@ +/* clang-format off */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + */ +/* clang-format on */ +#pragma once + +// work_limit_timer_t is now an alias for termination_checker_t. +// This header exists for backward compatibility. +#include "termination_checker.hpp" diff --git a/cpp/src/utilities/work_unit_predictor.cpp b/cpp/src/utilities/work_unit_predictor.cpp new file mode 100644 index 0000000000..4c9512768d --- /dev/null +++ b/cpp/src/utilities/work_unit_predictor.cpp @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "work_unit_predictor.hpp" + +#include +#include +#include +#include +#include +#include + +#include "models/fj_predictor/header.h" + +namespace cuopt { + +template +static inline uint32_t compute_hash(std::vector h_contents) +{ + // FNV-1a hash + + uint32_t hash = 2166136261u; // FNV-1a 32-bit offset basis + std::vector byte_contents(h_contents.size() * sizeof(i_t)); + std::memcpy(byte_contents.data(), h_contents.data(), h_contents.size() * sizeof(i_t)); + for (size_t i = 0; i < byte_contents.size(); ++i) { + hash ^= byte_contents[i]; + hash *= 16777619u; + } + return hash; +} + +template +float work_unit_predictor_t::predict_scalar( + const std::map& features) const +{ + raft::common::nvtx::range range("work_unit_predictor_t::predict_scalar"); + + typename model_t::Entry data[model_t::NUM_FEATURES]; + for (int i = 0; i < model_t::NUM_FEATURES; ++i) { + if (features.find(std::string(model_t::feature_names[i])) == features.end()) { + data[i].missing = -1; + CUOPT_LOG_WARN("Feature %s: missing\n", model_t::feature_names[i]); + } else { + data[i].fvalue = features.at(std::string(model_t::feature_names[i])); + } + } + + std::vector cache_vec; + cache_vec.reserve(model_t::NUM_FEATURES); + for (int i = 0; i < model_t::NUM_FEATURES; ++i) { + cache_vec.push_back(data[i].missing != -1 ? data[i].fvalue + : std::numeric_limits::quiet_NaN()); + } + uint32_t key = compute_hash(cache_vec); + + auto cached_it = prediction_cache.find(key); + if (cached_it != prediction_cache.end()) { return cached_it->second; } + + double result = 0.0; + auto start = std::chrono::high_resolution_clock::now(); + model_t::predict(data, 0, &result); + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end - start; + if (debug) CUOPT_LOG_DEBUG("Prediction time: %f ms", elapsed.count()); + + float scaled_result = scaler_.scale_work_units(result); + prediction_cache[key] = scaled_result; + if (debug) CUOPT_LOG_DEBUG("Result: %f (scaled: %f)", result, scaled_result); + + return scaled_result; +} + +template class work_unit_predictor_t; + +} // namespace cuopt diff --git a/cpp/src/utilities/work_unit_predictor.hpp b/cpp/src/utilities/work_unit_predictor.hpp new file mode 100644 index 0000000000..9d445c437e --- /dev/null +++ b/cpp/src/utilities/work_unit_predictor.hpp @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace cuopt { + +// Temporary scaling classes until I figure out better ways to do this +// to account for performance differences between the regression learning machine and the user +// machine. (e.g. integrate memory latency/bandwidth, cache topology, user-provided tuning...) +struct cpu_work_unit_scaler_t { + cpu_work_unit_scaler_t() + { + constexpr double baseline_max_clock = 3800.0; + double max_clock = get_cpu_max_clock_mhz(); + if (max_clock <= 0.0) { max_clock = baseline_max_clock; } + scaling_factor_ = baseline_max_clock / max_clock; + } + + double scale_work_units(double work_units) const { return work_units * scaling_factor_; } + + private: + double scaling_factor_; +}; + +struct gpu_work_unit_scaler_t { + double scale_work_units(double work_units) const { return work_units; } +}; + +template +class work_unit_predictor_t { + public: + float predict_scalar(const std::map& features) const; + + public: + bool debug{false}; + + private: + mutable std::unordered_map prediction_cache; + scaler_t scaler_; +}; + +} // namespace cuopt diff --git a/cpp/src/utilities/work_unit_scheduler.cpp b/cpp/src/utilities/work_unit_scheduler.cpp index b0e5c5f12f..84088b10e8 100644 --- a/cpp/src/utilities/work_unit_scheduler.cpp +++ b/cpp/src/utilities/work_unit_scheduler.cpp @@ -79,8 +79,8 @@ void work_unit_scheduler_t::wait_for_next_sync(work_limit_context_t& ctx) { if (is_shutdown()) return; - double next_sync = current_sync_target(); - ctx.global_work_units_elapsed = next_sync; + double next_sync = current_sync_target(); + ctx.set_current_work(next_sync, false); wait_at_sync_point(ctx, next_sync); } diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index a73a3361ce..fe9dd4fde9 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -# cmake-format: off +# cmake-format: off # SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # cmake-format: on @@ -33,6 +33,40 @@ endif() set(CUOPT_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +if (EXISTS "${CUDAToolkit_LIBRARY_ROOT}/extras/CUPTI/lib64") + # NVIDIA installer layout: + set(cuopt_cupti_root "${CUDAToolkit_LIBRARY_ROOT}/extras/CUPTI") +else() + # Ubuntu package layout: + set(cuopt_cupti_root "${CUDAToolkit_LIBRARY_ROOT}") +endif() +message(STATUS "cuopt_cupti_root = ${cuopt_cupti_root}") + +# The CUPTI targets in FindCUDAToolkit are broken: +# - The dll locations are not specified +# - Dependent libraries nvperf_* are not linked. +# So we create our own targets: +function(cuopt_add_cupti_dep dep_name) + string(TOLOWER ${dep_name} dep_name_lower) + string(TOUPPER ${dep_name} dep_name_upper) + + add_library(cuopt::${dep_name_lower} SHARED IMPORTED) + + find_library(CUOPT_${dep_name_upper}_LIBRARY ${dep_name_lower} REQUIRED + DOC "The full path to lib${dep_name_lower}.so from the CUDA Toolkit." + HINTS "${cuopt_cupti_root}/lib64" "${cuopt_cupti_root}/lib" + ) + mark_as_advanced(CUOPT_${dep_name_upper}_LIBRARY) + + set_target_properties(cuopt::${dep_name_lower} PROPERTIES + IMPORTED_LOCATION "${CUOPT_${dep_name_upper}_LIBRARY}" + ) +endfunction() + +#cuopt_add_cupti_dep(nvperf_target) +#cuopt_add_cupti_dep(nvperf_host) +#cuopt_add_cupti_dep(cupti) + # ################################################################ ------------------------------------------------------------------ function(ConfigureTest CMAKE_TEST_NAME) add_executable(${CMAKE_TEST_NAME} ${ARGN}) diff --git a/cpp/tests/mip/CMakeLists.txt b/cpp/tests/mip/CMakeLists.txt index 2f2139890f..24a4b3ddb5 100644 --- a/cpp/tests/mip/CMakeLists.txt +++ b/cpp/tests/mip/CMakeLists.txt @@ -40,12 +40,18 @@ ConfigureTest(PRESOLVE_TEST ${CMAKE_CURRENT_SOURCE_DIR}/presolve_test.cu ) # Disable for now -# ConfigureTest(FEASIBILITY_JUMP_TEST -# ${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump_tests.cu -# ) +ConfigureTest(FEASIBILITY_JUMP_TEST + ${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump_tests.cu +) ConfigureTest(MIP_TERMINATION_STATUS_TEST ${CMAKE_CURRENT_SOURCE_DIR}/termination_test.cu ) ConfigureTest(DETERMINISM_TEST ${CMAKE_CURRENT_SOURCE_DIR}/determinism_test.cu ) +ConfigureTest(LOCAL_SEARCH_TEST + ${CMAKE_CURRENT_SOURCE_DIR}/local_search_test.cu +) +ConfigureTest(DIVERSITY_TEST + ${CMAKE_CURRENT_SOURCE_DIR}/diversity_test.cu +) diff --git a/cpp/tests/mip/determinism_test.cu b/cpp/tests/mip/determinism_test.cu index 1e59fba649..0623cbf8f2 100644 --- a/cpp/tests/mip/determinism_test.cu +++ b/cpp/tests/mip/determinism_test.cu @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,7 @@ #include #include +#include #include #include @@ -31,6 +33,34 @@ namespace cuopt::linear_programming::test { namespace { +class scoped_env_var_t { + public: + scoped_env_var_t(const char* name, const char* value) : name_(name) + { + cuopt_assert(name != nullptr, "Environment variable name must be non-null"); + cuopt_assert(value != nullptr, "Environment variable value must be non-null"); + const char* original_value = std::getenv(name_); + was_set_ = (original_value != nullptr); + if (was_set_) { original_value_ = original_value; } + const int status = setenv(name_, value, 1); + assert(status == 0); + } + + ~scoped_env_var_t() + { + const int status = was_set_ ? setenv(name_, original_value_.c_str(), 1) : unsetenv(name_); + assert(status == 0); + } + + scoped_env_var_t(const scoped_env_var_t&) = delete; + scoped_env_var_t& operator=(const scoped_env_var_t&) = delete; + + private: + const char* name_; + std::string original_value_; + bool was_set_{false}; +}; + void expect_solutions_bitwise_equal(const mip_solution_t& sol1, const mip_solution_t& sol2, raft::handle_t& handle, @@ -45,6 +75,118 @@ void expect_solutions_bitwise_equal(const mip_solution_t& sol1, } } +struct callback_solution_t { + std::vector assignment; + double objective{}; + double solution_bound{}; + internals::mip_solution_origin_t origin{internals::mip_solution_origin_t::UNKNOWN}; +}; + +class first_n_get_solution_callback_t : public cuopt::internals::get_solution_callback_ext_t { + public: + first_n_get_solution_callback_t(std::vector& solutions_in, + int n_variables_, + size_t max_solutions_, + void* expected_user_data_) + : solutions(solutions_in), + expected_user_data(expected_user_data_), + n_variables(n_variables_), + max_solutions(max_solutions_) + { + } + + void get_solution(void* data, + void* cost, + void* solution_bound, + const internals::mip_solution_callback_info_t* callback_info, + void* user_data) override + { + EXPECT_EQ(user_data, expected_user_data); + ASSERT_NE(callback_info, nullptr); + EXPECT_GE(callback_info->struct_size, sizeof(internals::mip_solution_callback_info_t)); + n_calls++; + + auto assignment_ptr = static_cast(data); + auto objective_ptr = static_cast(cost); + auto solution_bound_ptr = static_cast(solution_bound); + EXPECT_FALSE(std::isnan(objective_ptr[0])); + EXPECT_FALSE(std::isnan(solution_bound_ptr[0])); + + if (solutions.size() >= max_solutions) { return; } + + callback_solution_t callback_solution; + callback_solution.assignment.assign(assignment_ptr, assignment_ptr + n_variables); + callback_solution.objective = objective_ptr[0]; + callback_solution.solution_bound = solution_bound_ptr[0]; + callback_solution.origin = callback_info->origin; + solutions.push_back(std::move(callback_solution)); + } + + std::vector& solutions; + void* expected_user_data; + int n_calls{0}; + int n_variables; + size_t max_solutions; +}; + +bool is_gpu_callback_origin(internals::mip_solution_origin_t origin) +{ + switch (origin) { + case internals::mip_solution_origin_t::FEASIBILITY_JUMP: + case internals::mip_solution_origin_t::LOCAL_SEARCH: + case internals::mip_solution_origin_t::QUICK_FEASIBLE: + case internals::mip_solution_origin_t::LP_ROUNDING: + case internals::mip_solution_origin_t::RECOMBINATION: + case internals::mip_solution_origin_t::SUB_MIP: return true; + default: return false; + } +} + +size_t count_callbacks_with_origin(const std::vector& callbacks, + internals::mip_solution_origin_t origin) +{ + return std::count_if(callbacks.begin(), + callbacks.end(), + [origin](const callback_solution_t& sol) { return sol.origin == origin; }); +} + +size_t count_gpu_callbacks(const std::vector& callbacks) +{ + return std::count_if(callbacks.begin(), callbacks.end(), [](const callback_solution_t& sol) { + return is_gpu_callback_origin(sol.origin); + }); +} + +size_t count_branch_and_bound_callbacks(const std::vector& callbacks) +{ + return std::count_if(callbacks.begin(), callbacks.end(), [](const callback_solution_t& sol) { + return sol.origin == internals::mip_solution_origin_t::BRANCH_AND_BOUND_NODE || + sol.origin == internals::mip_solution_origin_t::BRANCH_AND_BOUND_DIVING; + }); +} + +void expect_callback_prefixes_bitwise_equal(const std::vector& lhs, + const std::vector& rhs, + size_t prefix_size, + const std::string& label) +{ + ASSERT_GE(lhs.size(), prefix_size) << label << "Left callback prefix missing entries"; + ASSERT_GE(rhs.size(), prefix_size) << label << "Right callback prefix missing entries"; + for (size_t i = 0; i < prefix_size; ++i) { + EXPECT_EQ(lhs[i].objective, rhs[i].objective) + << label << "Callback objective differs at index " << i; + EXPECT_EQ(lhs[i].solution_bound, rhs[i].solution_bound) + << label << "Callback bound differs at index " << i; + EXPECT_EQ(lhs[i].origin, rhs[i].origin) << label << "Callback origin differs at index " << i; + ASSERT_EQ(lhs[i].assignment.size(), rhs[i].assignment.size()) + << label << "Callback assignment size differs at index " << i; + for (size_t j = 0; j < lhs[i].assignment.size(); ++j) { + EXPECT_EQ(lhs[i].assignment[j], rhs[i].assignment[j]) + << label << "Callback assignment differs at callback " << i << " variable " << j; + } + } +} + } // namespace class DeterministicBBTest : public ::testing::Test { @@ -61,7 +203,7 @@ TEST_F(DeterministicBBTest, reproducible_objective) mip_solver_settings_t settings; settings.time_limit = 60.0; - settings.determinism_mode = CUOPT_MODE_DETERMINISTIC; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC_BB; settings.num_cpu_threads = 8; settings.work_limit = 4; @@ -93,7 +235,7 @@ TEST_F(DeterministicBBTest, reproducible_infeasibility) mip_solver_settings_t settings; settings.time_limit = 60.0; - settings.determinism_mode = CUOPT_MODE_DETERMINISTIC; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC_BB; settings.num_cpu_threads = 8; settings.work_limit = 100; // High enough to fully explore @@ -125,7 +267,7 @@ TEST_F(DeterministicBBTest, reproducible_high_contention) mip_solver_settings_t settings; settings.time_limit = 60.0; - settings.determinism_mode = CUOPT_MODE_DETERMINISTIC; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC_BB; settings.num_cpu_threads = 128; // High thread count to stress contention settings.work_limit = 1; @@ -160,7 +302,7 @@ TEST_F(DeterministicBBTest, reproducible_solution_vector) mip_solver_settings_t settings; settings.time_limit = 60.0; - settings.determinism_mode = CUOPT_MODE_DETERMINISTIC; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC_BB; settings.num_cpu_threads = 8; settings.work_limit = 2; @@ -177,6 +319,250 @@ TEST_F(DeterministicBBTest, reproducible_solution_vector) expect_solutions_bitwise_equal(solution1, solution2, handle_); } +TEST_F(DeterministicBBTest, reproducible_with_gpu_pipeline_in_deterministic_mode) +{ + auto path = make_path_absolute("/mip/50v-10.mps"); + auto problem = mps_parser::parse_mps(path, false); + handle_.sync_stream(); + + mip_solver_settings_t settings; + settings.time_limit = 60.0; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC; + settings.num_cpu_threads = 8; + settings.work_limit = 30; + settings.gpu_heur_work_unit_scale = 0.1; + settings.cpufj_work_unit_scale = 1.0; + + auto seed = std::random_device{}() & 0x7fffffff; + std::cout << "Tested with seed " << seed << "\n"; + settings.seed = seed; + + cuopt::seed_generator::set_seed(seed); + auto solution1 = solve_mip(&handle_, problem, settings); + cuopt::seed_generator::set_seed(seed); + auto solution2 = solve_mip(&handle_, problem, settings); + cuopt::seed_generator::set_seed(seed); + auto solution3 = solve_mip(&handle_, problem, settings); + + EXPECT_EQ(solution1.get_termination_status(), solution2.get_termination_status()); + EXPECT_EQ(solution1.get_termination_status(), solution3.get_termination_status()); + + EXPECT_DOUBLE_EQ(solution1.get_objective_value(), solution2.get_objective_value()); + EXPECT_DOUBLE_EQ(solution1.get_objective_value(), solution3.get_objective_value()); + + EXPECT_DOUBLE_EQ(solution1.get_solution_bound(), solution2.get_solution_bound()); + EXPECT_DOUBLE_EQ(solution1.get_solution_bound(), solution3.get_solution_bound()); + + expect_solutions_bitwise_equal( + solution1, solution2, handle_, "Deterministic GPU pipeline run 1 vs 2: "); + expect_solutions_bitwise_equal( + solution1, solution3, handle_, "Deterministic GPU pipeline run 1 vs 3: "); +} + +TEST_F(DeterministicBBTest, deterministic_gpu_pipeline_ignores_cpufj_work_scale) +{ + auto path = make_path_absolute("/mip/50v-10.mps"); + auto problem = mps_parser::parse_mps(path, false); + handle_.sync_stream(); + + mip_solver_settings_t base_settings; + base_settings.time_limit = 60.0; + base_settings.determinism_mode = CUOPT_MODE_DETERMINISTIC; + base_settings.num_cpu_threads = 8; + base_settings.work_limit = 30; + base_settings.gpu_heur_work_unit_scale = 0.1; + + auto seed = std::random_device{}() & 0x7fffffff; + std::cout << "Tested with seed " << seed << "\n"; + base_settings.seed = seed; + + auto settings_without_cpufj = base_settings; + settings_without_cpufj.cpufj_work_unit_scale = 1.0; + cuopt::seed_generator::set_seed(seed); + auto solution_without_cpufj = solve_mip(&handle_, problem, settings_without_cpufj); + + auto settings_with_cpufj_scale = base_settings; + settings_with_cpufj_scale.cpufj_work_unit_scale = 17.0; + cuopt::seed_generator::set_seed(seed); + auto solution_with_cpufj_scale = solve_mip(&handle_, problem, settings_with_cpufj_scale); + + EXPECT_EQ(solution_without_cpufj.get_termination_status(), + solution_with_cpufj_scale.get_termination_status()); + EXPECT_DOUBLE_EQ(solution_without_cpufj.get_objective_value(), + solution_with_cpufj_scale.get_objective_value()); + EXPECT_DOUBLE_EQ(solution_without_cpufj.get_solution_bound(), + solution_with_cpufj_scale.get_solution_bound()); + expect_solutions_bitwise_equal(solution_without_cpufj, + solution_with_cpufj_scale, + handle_, + "Deterministic GPU pipeline should ignore CPUFJ scale: "); +} + +TEST_F(DeterministicBBTest, deterministic_callback_sequence_reproducible_with_gpu_pipeline) +{ + constexpr size_t callback_compare_count = 5; + constexpr size_t callback_capture_limit = 32; + constexpr size_t min_gpu_callback_count = 3; + constexpr size_t min_bnb_callback_count = 3; + + auto path = make_path_absolute("/mip/50v-10.mps"); + auto problem = mps_parser::parse_mps(path, false); + handle_.sync_stream(); + + mip_solver_settings_t settings; + settings.time_limit = 360.0; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC; + settings.num_cpu_threads = 2; + settings.work_limit = 60; + settings.gpu_heur_work_unit_scale = 0.05; + settings.cpufj_work_unit_scale = 1.0; + + auto seed = std::random_device{}() & 0x7fffffff; + std::cout << "Tested with seed " << seed << "\n"; + settings.seed = seed; + + const int n_variables = problem.get_variable_lower_bounds().size(); + int user_data = 7; + + std::vector callbacks_run1; + first_n_get_solution_callback_t callback_run1( + callbacks_run1, n_variables, callback_capture_limit, &user_data); + auto settings_run1 = settings; + settings_run1.set_mip_callback(&callback_run1, &user_data); + cuopt::seed_generator::set_seed(seed); + auto solution1 = solve_mip(&handle_, problem, settings_run1); + + std::vector callbacks_run2; + first_n_get_solution_callback_t callback_run2( + callbacks_run2, n_variables, callback_capture_limit, &user_data); + auto settings_run2 = settings; + settings_run2.set_mip_callback(&callback_run2, &user_data); + cuopt::seed_generator::set_seed(seed); + auto solution2 = solve_mip(&handle_, problem, settings_run2); + + std::vector callbacks_run3; + first_n_get_solution_callback_t callback_run3( + callbacks_run3, n_variables, callback_capture_limit, &user_data); + auto settings_run3 = settings; + settings_run3.set_mip_callback(&callback_run3, &user_data); + cuopt::seed_generator::set_seed(seed); + auto solution3 = solve_mip(&handle_, problem, settings_run3); + + EXPECT_EQ(solution1.get_termination_status(), solution2.get_termination_status()); + EXPECT_EQ(solution1.get_termination_status(), solution3.get_termination_status()); + EXPECT_GE(callback_run1.n_calls, (int)callback_compare_count); + EXPECT_GE(callback_run2.n_calls, (int)callback_compare_count); + EXPECT_GE(callback_run3.n_calls, (int)callback_compare_count); + ASSERT_GE(callbacks_run1.size(), callback_compare_count); + ASSERT_GE(callbacks_run2.size(), callback_compare_count); + ASSERT_GE(callbacks_run3.size(), callback_compare_count); + + EXPECT_GE(count_gpu_callbacks(callbacks_run1), min_gpu_callback_count); + EXPECT_GE(count_gpu_callbacks(callbacks_run2), min_gpu_callback_count); + EXPECT_GE(count_gpu_callbacks(callbacks_run3), min_gpu_callback_count); + EXPECT_GE(count_branch_and_bound_callbacks(callbacks_run1), min_bnb_callback_count); + EXPECT_GE(count_branch_and_bound_callbacks(callbacks_run2), min_bnb_callback_count); + EXPECT_GE(count_branch_and_bound_callbacks(callbacks_run3), min_bnb_callback_count); + + expect_callback_prefixes_bitwise_equal( + callbacks_run1, callbacks_run2, callback_compare_count, "Deterministic callback run 1 vs 2: "); + expect_callback_prefixes_bitwise_equal( + callbacks_run1, callbacks_run3, callback_compare_count, "Deterministic callback run 1 vs 3: "); +} + +class DeterministicGpuHeuristicsInstanceTest : public ::testing::TestWithParam { + protected: + raft::handle_t handle_; +}; + +TEST_P(DeterministicGpuHeuristicsInstanceTest, reproducible_with_gpu_heuristics) +{ + auto path = make_path_absolute(GetParam()); + auto problem = mps_parser::parse_mps(path, false); + handle_.sync_stream(); + + mip_solver_settings_t settings; + settings.time_limit = 60.0; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC; + settings.num_cpu_threads = 8; + settings.work_limit = 30; + + auto seed = std::random_device{}() & 0x7fffffff; + std::cout << "Tested with seed " << seed << "\n"; + settings.seed = seed; + + cuopt::seed_generator::set_seed(seed); + auto solution1 = solve_mip(&handle_, problem, settings); + cuopt::seed_generator::set_seed(seed); + auto solution2 = solve_mip(&handle_, problem, settings); + cuopt::seed_generator::set_seed(seed); + auto solution3 = solve_mip(&handle_, problem, settings); + + EXPECT_EQ(solution1.get_termination_status(), solution2.get_termination_status()); + EXPECT_EQ(solution1.get_termination_status(), solution3.get_termination_status()); + + EXPECT_DOUBLE_EQ(solution1.get_objective_value(), solution2.get_objective_value()); + EXPECT_DOUBLE_EQ(solution1.get_objective_value(), solution3.get_objective_value()); + + EXPECT_DOUBLE_EQ(solution1.get_solution_bound(), solution2.get_solution_bound()); + EXPECT_DOUBLE_EQ(solution1.get_solution_bound(), solution3.get_solution_bound()); + + expect_solutions_bitwise_equal(solution1, solution2, handle_, "GPU heur run 1 vs 2: "); + expect_solutions_bitwise_equal(solution1, solution3, handle_, "GPU heur run 1 vs 3: "); +} + +TEST_F(DeterministicBBTest, reproducible_with_gpu_heuristics_50v10_no_cuts) +{ + auto path = make_path_absolute("/mip/50v-10.mps"); + auto problem = mps_parser::parse_mps(path, false); + handle_.sync_stream(); + + mip_solver_settings_t settings; + settings.time_limit = 60.0; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC; + settings.num_cpu_threads = 8; + settings.work_limit = 30; + // settings.max_cut_passes = 0; + + auto seed = std::random_device{}() & 0x7fffffff; + std::cout << "Tested with seed " << seed << "\n"; + settings.seed = seed; + + cuopt::seed_generator::set_seed(seed); + auto solution1 = solve_mip(&handle_, problem, settings); + cuopt::seed_generator::set_seed(seed); + auto solution2 = solve_mip(&handle_, problem, settings); + cuopt::seed_generator::set_seed(seed); + auto solution3 = solve_mip(&handle_, problem, settings); + + EXPECT_EQ(solution1.get_termination_status(), solution2.get_termination_status()); + EXPECT_EQ(solution1.get_termination_status(), solution3.get_termination_status()); + + EXPECT_DOUBLE_EQ(solution1.get_objective_value(), solution2.get_objective_value()); + EXPECT_DOUBLE_EQ(solution1.get_objective_value(), solution3.get_objective_value()); + + EXPECT_DOUBLE_EQ(solution1.get_solution_bound(), solution2.get_solution_bound()); + EXPECT_DOUBLE_EQ(solution1.get_solution_bound(), solution3.get_solution_bound()); + + expect_solutions_bitwise_equal(solution1, solution2, handle_, "GPU heur no-cuts run 1 vs 2: "); + expect_solutions_bitwise_equal(solution1, solution3, handle_, "GPU heur no-cuts run 1 vs 3: "); +} + +INSTANTIATE_TEST_SUITE_P( + DeterministicGpuHeuristics, + DeterministicGpuHeuristicsInstanceTest, + ::testing::Values(std::string("/mip/gen-ip054.mps"), + std::string("/mip/pk1.mps"), + // std::string("/mip/sct2.mps"), + // std::string("/mip/thor50dday.mps"), + std::string("/mip/50v-10.mps")), + [](const ::testing::TestParamInfo& info) { + std::string name = info.param.substr(info.param.rfind('/') + 1); + name = name.substr(0, name.rfind('.')); + std::replace(name.begin(), name.end(), '-', '_'); + return name; + }); + // Parameterized test for different problem instances class DeterministicBBInstanceTest : public ::testing::TestWithParam> { @@ -186,6 +572,7 @@ class DeterministicBBInstanceTest TEST_P(DeterministicBBInstanceTest, deterministic_across_runs) { + // scoped_env_var_t gpu_fj_work_scale("CUOPT_GPU_HEUR_WORK_UNIT_SCALE", "0.1"); auto [instance_path, num_threads, time_limit, work_limit] = GetParam(); auto path = make_path_absolute(instance_path); auto problem = mps_parser::parse_mps(path, false); @@ -230,6 +617,7 @@ INSTANTIATE_TEST_SUITE_P( // Instance, threads, time_limit std::make_tuple("/mip/gen-ip054.mps", 4, 60.0, 4), std::make_tuple("/mip/swath1.mps", 8, 60.0, 4), + std::make_tuple("/mip/50v-10.mps", 8, 60.0, 30), std::make_tuple("/mip/gen-ip054.mps", 128, 120.0, 1), std::make_tuple("/mip/bb_optimality.mps", 4, 60.0, 4), std::make_tuple("/mip/neos5.mps", 16, 60.0, 1), diff --git a/cpp/tests/mip/determinism_utils.cuh b/cpp/tests/mip/determinism_utils.cuh new file mode 100644 index 0000000000..28e8c4b51b --- /dev/null +++ b/cpp/tests/mip/determinism_utils.cuh @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +#include +#include + +#include + +namespace cuopt::linear_programming::test { + +static __global__ void spin_kernel(int* flag, unsigned long long timeout_clocks = 10000000) +{ + cuda::atomic_ref flag_ref(*flag); + + long long int start_clock, sample_clock; + start_clock = clock64(); + + while (flag_ref.load() == 0) { + sample_clock = clock64(); + + if (sample_clock - start_clock > timeout_clocks) { break; } + } +} + +static void launch_spin_kernel_stream_thread(rmm::cuda_stream_view stream_view, int* flag) +{ + while (true) { + int blocks = rand() % 64 + 1; + int threads = rand() % 1024 + 1; + spin_kernel<<>>(flag); + cudaStreamSynchronize(stream_view); + if (host_copy(flag, 1, stream_view)[0] != 0) { break; } + std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 1000 + 1)); + } +} + +class spin_stream_raii_t { + public: + spin_stream_raii_t() + : flag(0, stream), spin_thread(launch_spin_kernel_stream_thread, stream.view(), flag.data()) + { + } + + ~spin_stream_raii_t() + { + int one = 1; + flag.set_value_async(one, stream); + spin_thread.join(); + } + + private: + rmm::cuda_stream stream; + rmm::device_scalar flag; + std::thread spin_thread; +}; + +} // namespace cuopt::linear_programming::test \ No newline at end of file diff --git a/cpp/tests/mip/diversity_test.cu b/cpp/tests/mip/diversity_test.cu new file mode 100644 index 0000000000..bfe8c46da0 --- /dev/null +++ b/cpp/tests/mip/diversity_test.cu @@ -0,0 +1,428 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../linear_programming/utilities/pdlp_test_utilities.cuh" +#include "determinism_utils.cuh" +#include "mip_utils.cuh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace cuopt::linear_programming::test { + +void init_handler(const raft::handle_t* handle_ptr) +{ + // Init cuBlas / cuSparse context here to avoid having it during solving time + RAFT_CUBLAS_TRY(raft::linalg::detail::cublassetpointermode( + handle_ptr->get_cublas_handle(), CUBLAS_POINTER_MODE_DEVICE, handle_ptr->get_stream())); + RAFT_CUSPARSE_TRY(raft::sparse::detail::cusparsesetpointermode( + handle_ptr->get_cusparse_handle(), CUSPARSE_POINTER_MODE_DEVICE, handle_ptr->get_stream())); +} + +static void setup_device_symbols(rmm::cuda_stream_view stream_view) { (void)stream_view; } + +static uint32_t test_full_run_determinism(std::string path, + unsigned long seed = std::random_device{}(), + float work_limit = 10.0f) +{ + const raft::handle_t handle_{}; + + cuopt::mps_parser::mps_data_model_t mps_problem = + cuopt::mps_parser::parse_mps(path, false); + handle_.sync_stream(); + auto op_problem = mps_data_model_to_optimization_problem(&handle_, mps_problem); + problem_checking_t::check_problem_representation(op_problem); + + init_handler(op_problem.get_handle_ptr()); + // run the problem constructor of MIP, so that we do bounds standardization + detail::problem_t problem(op_problem); + problem.deterministic = true; + problem.preprocess_problem(); + + setup_device_symbols(op_problem.get_handle_ptr()->get_stream()); + + pdlp_hyper_params::pdlp_hyper_params_t hyper_params{}; + detail::pdlp_initial_scaling_strategy_t scaling(&handle_, + problem, + 10, + 1.0, + problem.reverse_coefficients, + problem.reverse_offsets, + problem.reverse_constraints, + nullptr, + hyper_params, + true); + + auto settings = mip_solver_settings_t{}; + settings.time_limit = 3000.; + settings.work_limit = work_limit; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC_GPU_HEURISTICS; + settings.heuristics_only = true; + auto timer = cuopt::termination_checker_t(3000.0, cuopt::termination_checker_t::root_tag_t{}); + detail::mip_solver_t solver(problem, settings, scaling, timer); + problem.tolerances = settings.get_tolerances(); + + detail::diversity_manager_t diversity_manager(solver.context); + solver.context.gpu_heur_loop.deterministic = true; + diversity_manager.timer = + work_limit_timer_t(solver.context.gpu_heur_loop, settings.work_limit, timer); + diversity_manager.run_solver(); + + std::vector hashes; + auto pop = diversity_manager.get_population_pointer(); + for (const auto& sol : pop->population_to_vector()) { + hashes.push_back(sol.get_hash()); + } + + uint32_t final_hash = detail::compute_hash(hashes); + printf("%s: final hash: 0x%x, pop size %d\n", + path.c_str(), + final_hash, + (int)pop->population_to_vector().size()); + return final_hash; +} + +static uint32_t test_initial_solution_determinism(std::string path, + unsigned long seed = std::random_device{}()) +{ + const raft::handle_t handle_{}; + + cuopt::mps_parser::mps_data_model_t mps_problem = + cuopt::mps_parser::parse_mps(path, false); + handle_.sync_stream(); + auto op_problem = mps_data_model_to_optimization_problem(&handle_, mps_problem); + problem_checking_t::check_problem_representation(op_problem); + + init_handler(op_problem.get_handle_ptr()); + // run the problem constructor of MIP, so that we do bounds standardization + detail::problem_t problem(op_problem); + problem.deterministic = true; + problem.preprocess_problem(); + + setup_device_symbols(op_problem.get_handle_ptr()->get_stream()); + + pdlp_hyper_params::pdlp_hyper_params_t hyper_params{}; + detail::pdlp_initial_scaling_strategy_t scaling(&handle_, + problem, + 10, + 1.0, + problem.reverse_coefficients, + problem.reverse_offsets, + problem.reverse_constraints, + nullptr, + hyper_params, + true); + + auto settings = mip_solver_settings_t{}; + settings.time_limit = 3000.; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC_GPU_HEURISTICS; + settings.heuristics_only = true; + auto timer = cuopt::termination_checker_t(3000.0, cuopt::termination_checker_t::root_tag_t{}); + detail::mip_solver_t solver(problem, settings, scaling, timer); + problem.tolerances = settings.get_tolerances(); + + detail::diversity_manager_t diversity_manager(solver.context); + work_limit_context_t work_limit_context("DiversityManager"); + work_limit_context.deterministic = true; + diversity_manager.timer = work_limit_timer_t(work_limit_context, 60000, timer); + diversity_manager.diversity_config.initial_solution_only = true; + diversity_manager.run_solver(); + + std::vector hashes; + auto pop = diversity_manager.get_population_pointer(); + for (const auto& sol : pop->population_to_vector()) { + hashes.push_back(sol.get_hash()); + } + + uint32_t final_hash = detail::compute_hash(hashes); + printf("%s: final hash: 0x%x, pop size %d\n", + path.c_str(), + final_hash, + (int)pop->population_to_vector().size()); + return final_hash; +} + +static uint32_t test_recombiners_determinism(std::string path, + unsigned long seed = std::random_device{}()) +{ + const raft::handle_t handle_{}; + + cuopt::mps_parser::mps_data_model_t mps_problem = + cuopt::mps_parser::parse_mps(path, false); + handle_.sync_stream(); + auto op_problem = mps_data_model_to_optimization_problem(&handle_, mps_problem); + problem_checking_t::check_problem_representation(op_problem); + + init_handler(op_problem.get_handle_ptr()); + // run the problem constructor of MIP, so that we do bounds standardization + detail::problem_t problem(op_problem); + problem.deterministic = true; + problem.preprocess_problem(); + + setup_device_symbols(op_problem.get_handle_ptr()->get_stream()); + + pdlp_hyper_params::pdlp_hyper_params_t hyper_params{}; + detail::pdlp_initial_scaling_strategy_t scaling(&handle_, + problem, + 10, + 1.0, + problem.reverse_coefficients, + problem.reverse_offsets, + problem.reverse_constraints, + nullptr, + hyper_params, + true); + + auto settings = mip_solver_settings_t{}; + settings.time_limit = 3000.; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC_GPU_HEURISTICS; + settings.heuristics_only = true; + auto timer = cuopt::termination_checker_t(3000.0, cuopt::termination_checker_t::root_tag_t{}); + detail::mip_solver_t solver(problem, settings, scaling, timer); + problem.tolerances = settings.get_tolerances(); + + detail::diversity_manager_t diversity_manager(solver.context); + work_limit_context_t work_limit_context("DiversityManager"); + work_limit_context.deterministic = true; + diversity_manager.timer = work_limit_timer_t(work_limit_context, 60000, timer); + diversity_manager.diversity_config.dry_run = true; + diversity_manager.run_solver(); + + // Generate a population by running FJ on random starting points + // recombine a few solutions, observe the output + for (int i = diversity_manager.population.current_size(); i < 3; ++i) { + detail::solution_t random_initial_solution(problem); + random_initial_solution.assign_random_within_bounds(); + detail::fj_settings_t fj_settings; + fj_settings.feasibility_run = false; + fj_settings.iteration_limit = 1000 + i * 100; + fj_settings.seed = seed + i; + auto solution = run_fj(problem, + fj_settings, + fj_tweaks_t{}, + random_initial_solution.get_host_assignment(), + CUOPT_MODE_DETERMINISTIC) + .solution; + printf("population %d hash: 0x%x\n", i, solution.get_hash()); + diversity_manager.population.add_solution(std::move(solution), + internals::mip_solution_origin_t::FEASIBILITY_JUMP); + } + + auto pop_vector = diversity_manager.get_population_pointer()->population_to_vector(); + int pop_size = std::min(6, (int)pop_vector.size()); + + std::vector hashes; + + static std::map, uint32_t> hash_map; + + for (auto recombiner : {detail::recombiner_enum_t::LINE_SEGMENT, + detail::recombiner_enum_t::BOUND_PROP, + detail::recombiner_enum_t::FP}) { + for (int i = 1; i < pop_size; i++) { + for (int j = i + 1; j < pop_size; j++) { + printf("recombining %d and %d w/ recombiner %s\n", + i, + j, + detail::all_recombine_stats::recombiner_labels[(int)recombiner]); + auto [offspring, success] = + diversity_manager.recombine(pop_vector[i], pop_vector[j], recombiner); + auto offspring_hash = offspring.get_hash(); + printf("for %d,%d: offspring hash: 0x%x, parent 1 hash: 0x%x, parent 2 hash: 0x%x\n", + i, + j, + offspring_hash, + pop_vector[i].get_hash(), + pop_vector[j].get_hash()); + if (hash_map.find(std::make_tuple(path, i, j, recombiner)) == hash_map.end()) { + hash_map[std::make_tuple(path, i, j, recombiner)] = offspring_hash; + } else { + if (hash_map[std::make_tuple(path, i, j, recombiner)] != offspring_hash) { + printf("%s: hash mismatch for %d,%d: %d != %d\n", + path.c_str(), + i, + j, + hash_map[std::make_tuple(path, i, j, recombiner)], + offspring_hash); + exit(1); + } + } + hashes.push_back(offspring_hash); + } + } + } + return detail::compute_hash(hashes); + + auto pop = diversity_manager.get_population_pointer(); + for (const auto& sol : pop->population_to_vector()) { + hashes.push_back(sol.get_hash()); + } + + uint32_t final_hash = detail::compute_hash(hashes); + printf("%s: final hash: 0x%x, pop size %d\n", + path.c_str(), + final_hash, + (int)pop->population_to_vector().size()); + return final_hash; +} + +class DiversityTestParams : public testing::TestWithParam> {}; + +TEST_P(DiversityTestParams, recombiners_deterministic) +{ + // cuopt::init_logger_t log("", true); + cuopt::default_logger().set_pattern("[%n] [%-6l] %v"); + cuopt::default_logger().set_level(rapids_logger::level_enum::debug); + cuopt::default_logger().flush_on(rapids_logger::level_enum::debug); + + spin_stream_raii_t spin_stream_1; + spin_stream_raii_t spin_stream_2; + + auto test_instance = std::get<0>(GetParam()); + std::cout << "Running: " << test_instance << std::endl; + int seed = + std::getenv("CUOPT_SEED") ? std::stoi(std::getenv("CUOPT_SEED")) : std::random_device{}(); + std::cerr << "Tested with seed " << seed << "\n"; + auto path = make_path_absolute(test_instance); + test_instance = std::getenv("CUOPT_INSTANCE") ? std::getenv("CUOPT_INSTANCE") : test_instance; + uint32_t gold_hash = 0; + for (int i = 0; i < 2; ++i) { + cuopt::seed_generator::set_seed(seed); + std::cout << "Running " << test_instance << " " << i << std::endl; + std::cout << "-------------------------------------------------------------\n"; + auto hash = test_recombiners_determinism(path, seed); + if (i == 0) { + gold_hash = hash; + std::cout << "Gold hash: " << gold_hash << std::endl; + } else { + ASSERT_EQ(hash, gold_hash); + } + } +} + +TEST_P(DiversityTestParams, initial_solution_deterministic) +{ + cuopt::default_logger().set_pattern("[%n] [%-6l] %v"); + + spin_stream_raii_t spin_stream_1; + spin_stream_raii_t spin_stream_2; + + auto test_instance = std::get<0>(GetParam()); + std::cout << "Running: " << test_instance << std::endl; + int seed = + std::getenv("CUOPT_SEED") ? std::stoi(std::getenv("CUOPT_SEED")) : std::random_device{}(); + std::cerr << "Tested with seed " << seed << "\n"; + auto path = make_path_absolute(test_instance); + test_instance = std::getenv("CUOPT_INSTANCE") ? std::getenv("CUOPT_INSTANCE") : test_instance; + uint32_t gold_hash = 0; + for (int i = 0; i < 2; ++i) { + cuopt::seed_generator::set_seed(seed); + std::cout << "Running " << test_instance << " " << i << std::endl; + std::cout << "-------------------------------------------------------------\n"; + auto hash = test_initial_solution_determinism(path, seed); + if (i == 0) { + gold_hash = hash; + std::cout << "Gold hash: " << gold_hash << std::endl; + } else { + ASSERT_EQ(hash, gold_hash); + } + } +} + +TEST_P(DiversityTestParams, full_run_deterministic) +{ + cuopt::init_logger_t log("", true); + // cuopt::default_logger().set_pattern("[%n] [%-6l] %v"); + cuopt::default_logger().set_level(rapids_logger::level_enum::debug); + cuopt::default_logger().flush_on(rapids_logger::level_enum::debug); + + // spin_stream_raii_t spin_stream_1; + // spin_stream_raii_t spin_stream_2; + + auto test_instance = std::get<0>(GetParam()); + const float work_limit = std::get<1>(GetParam()); + std::cout << "Running: " << test_instance << std::endl; + int seed = + std::getenv("CUOPT_SEED") ? std::stoi(std::getenv("CUOPT_SEED")) : std::random_device{}(); + std::cerr << "Tested with seed " << seed << "\n"; + auto path = make_path_absolute(test_instance); + if (std::getenv("CUOPT_INSTANCE")) { + test_instance = std::getenv("CUOPT_INSTANCE"); + path = make_path_absolute(test_instance); + } + uint32_t gold_hash = 0; + for (int i = 0; i < 4; ++i) { + cuopt::seed_generator::set_seed(seed); + std::cout << "Running " << test_instance << " " << i << std::endl; + std::cout << "-------------------------------------------------------------\n"; + auto hash = test_full_run_determinism(path, seed, work_limit); + if (i == 0) { + gold_hash = hash; + std::cout << "Gold hash: " << gold_hash << std::endl; + } else { + ASSERT_EQ(hash, gold_hash); + } + } +} + +INSTANTIATE_TEST_SUITE_P(DiversityTest, + DiversityTestParams, + testing::Values( + // std::make_tuple("mip/gen-ip054.mps", 5.0f), + // std::make_tuple("mip/pk1.mps", 5.0f), + std::make_tuple("mip/uccase9.mps", 5.0f), + std::make_tuple("mip/sct2.mps", 5.0f), + std::make_tuple("mip/thor50dday.mps", 5.0f), + // std::make_tuple("uccase9.mps"), + // std::make_tuple("mip/neos5.mps", 5.0f), + std::make_tuple("mip/50v-10.mps", 5.0f) + // std::make_tuple("mip/rmatr200-p5.mps", 5.0f) + )); + +} // namespace cuopt::linear_programming::test diff --git a/cpp/tests/mip/feasibility_jump_tests.cu b/cpp/tests/mip/feasibility_jump_tests.cu index baa3e9b803..b36fbcd316 100644 --- a/cpp/tests/mip/feasibility_jump_tests.cu +++ b/cpp/tests/mip/feasibility_jump_tests.cu @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -45,28 +46,23 @@ void init_handler(const raft::handle_t* handle_ptr) handle_ptr->get_cusparse_handle(), CUSPARSE_POINTER_MODE_DEVICE, handle_ptr->get_stream())); } -struct fj_tweaks_t { - double objective_weight = 0; -}; - -struct fj_state_t { - detail::solution_t solution; - std::vector solution_vector; - int minimums; - double incumbent_objective; - double incumbent_violation; -}; - // Helper function to setup MIP solver and run FJ with given settings and initial solution -static fj_state_t run_fj(std::string test_instance, - const detail::fj_settings_t& fj_settings, - fj_tweaks_t tweaks = {}, - std::vector initial_solution = {}) +static fj_state_t run_fj_instance(std::string test_instance, + const detail::fj_settings_t& fj_settings, + fj_tweaks_t tweaks = {}, + std::vector initial_solution = {}, + int determinism_mode = CUOPT_MODE_DETERMINISTIC) { const raft::handle_t handle_{}; std::cout << "Running: " << test_instance << std::endl; auto path = cuopt::test::get_rapids_dataset_root_dir() + ("/mip/" + test_instance); + + if (std::getenv("CUOPT_INSTANCE")) { + path = make_path_absolute(std::getenv("CUOPT_INSTANCE")); + std::cout << "Using instance from CUOPT_INSTANCE: " << path << std::endl; + } + cuopt::mps_parser::mps_data_model_t mps_problem = cuopt::mps_parser::parse_mps(path, false); handle_.sync_stream(); @@ -77,46 +73,8 @@ static fj_state_t run_fj(std::string test_instance, // run the problem constructor of MIP, so that we do bounds standardization detail::problem_t problem(op_problem); problem.preprocess_problem(); - detail::pdhg_solver_t pdhg_solver(problem.handle_ptr, problem); - detail::pdlp_initial_scaling_strategy_t scaling(&handle_, - problem, - 10, - 1.0, - pdhg_solver, - problem.reverse_coefficients, - problem.reverse_offsets, - problem.reverse_constraints, - true); - - auto settings = mip_solver_settings_t{}; - settings.time_limit = 30.; - auto timer = cuopt::timer_t(30); - detail::mip_solver_t solver(problem, settings, scaling, timer); - - detail::solution_t solution(*solver.context.problem_ptr); - if (initial_solution.size() > 0) { - expand_device_copy(solution.assignment, initial_solution, solution.handle_ptr->get_stream()); - } else { - thrust::fill(solution.handle_ptr->get_thrust_policy(), - solution.assignment.begin(), - solution.assignment.end(), - 0.0); - } - solution.clamp_within_bounds(); - - detail::fj_t fj(solver.context, fj_settings); - fj.reset_weights(solution.handle_ptr->get_stream(), 1.); - fj.objective_weight.set_value_async(tweaks.objective_weight, solution.handle_ptr->get_stream()); - solution.handle_ptr->sync_stream(); - fj.solve(solution); - auto solution_vector = host_copy(solution.assignment, solution.handle_ptr->get_stream()); - - return {solution, - solution_vector, - fj.climbers[0]->local_minimums_reached.value(solution.handle_ptr->get_stream()), - fj.climbers[0]->incumbent_objective.value(solution.handle_ptr->get_stream()), - fj.climbers[0]->violation_score.value(solution.handle_ptr->get_stream())}; + return run_fj(problem, fj_settings, tweaks, initial_solution, determinism_mode); } // FJ had a bug causing objective/violation values to explode in magnitude in certain scenarios. @@ -126,12 +84,12 @@ static bool run_fj_check_no_obj_runoff(std::string test_instance) detail::fj_settings_t fj_settings; fj_settings.time_limit = 30.; fj_settings.mode = detail::fj_mode_t::EXIT_NON_IMPROVING; - fj_settings.n_of_minimums_for_exit = 20000 * 1000; + fj_settings.n_of_minimums_for_exit = 5000; fj_settings.update_weights = true; fj_settings.feasibility_run = false; fj_settings.iteration_limit = 20000; - auto state = run_fj(test_instance, fj_settings); + auto state = run_fj_instance(test_instance, fj_settings); // ensure that the objective and the violation in the FJ state are not too large (<1e60) EXPECT_LE(state.incumbent_violation, 1e60) << "FJ violation too large"; @@ -148,12 +106,13 @@ static bool run_fj_check_objective(std::string test_instance, int iter_limit, do detail::fj_settings_t fj_settings; fj_settings.time_limit = 30.; fj_settings.mode = detail::fj_mode_t::EXIT_NON_IMPROVING; - fj_settings.n_of_minimums_for_exit = 20000 * 1000; + fj_settings.n_of_minimums_for_exit = 5000; fj_settings.update_weights = true; fj_settings.feasibility_run = obj_target == +std::numeric_limits::infinity(); fj_settings.iteration_limit = iter_limit; - auto state = run_fj(test_instance, fj_settings); + auto state = + run_fj_instance(test_instance, fj_settings, fj_tweaks_t{}, {}, CUOPT_MODE_DETERMINISTIC); auto& solution = state.solution; CUOPT_LOG_DEBUG("%s: Solution generated with FJ: is_feasible %d, objective %g (raw %g)", @@ -175,12 +134,12 @@ static bool run_fj_check_feasible(std::string test_instance) detail::fj_settings_t fj_settings; fj_settings.time_limit = 30.; fj_settings.mode = detail::fj_mode_t::EXIT_NON_IMPROVING; - fj_settings.n_of_minimums_for_exit = 20000 * 1000; + fj_settings.n_of_minimums_for_exit = 5000; fj_settings.update_weights = true; fj_settings.feasibility_run = false; fj_settings.iteration_limit = 25000; - auto state = run_fj(test_instance, fj_settings); + auto state = run_fj_instance(test_instance, fj_settings); auto& solution = state.solution; bool previous_feasible = solution.get_feasible(); @@ -191,8 +150,8 @@ static bool run_fj_check_feasible(std::string test_instance) // again but with very large obj weight to force FJ into the infeasible region fj_tweaks_t tweaks; tweaks.objective_weight = 1e6; - auto new_state = run_fj(test_instance, fj_settings, tweaks, state.solution_vector); - auto& new_solution = new_state.solution; + auto new_state = run_fj_instance(test_instance, fj_settings, tweaks, state.solution_vector); + auto& new_solution = new_state.solution; CUOPT_LOG_DEBUG("%s: Solution generated with FJ: is_feasible %d, objective %g (raw %g)", test_instance.c_str(), @@ -207,63 +166,119 @@ static bool run_fj_check_feasible(std::string test_instance) return true; } -class MIPSolveParametricTest : public testing::TestWithParam> { -}; - -TEST_P(MIPSolveParametricTest, feasibility_jump_obj_test) +static bool run_fj_check_determinism(std::string test_instance, int iter_limit) { - auto [instance, obj_target, iter_limit] = GetParam(); - EXPECT_TRUE(run_fj_check_objective(instance, iter_limit, obj_target)); -} + detail::fj_settings_t fj_settings; + fj_settings.time_limit = std::numeric_limits::max(); + fj_settings.mode = detail::fj_mode_t::EXIT_NON_IMPROVING; + fj_settings.n_of_minimums_for_exit = 5000 * 1000; + // fj_settings.work_limit = 0.5; // run for 0.5wu (~0.5s) + fj_settings.update_weights = true; + fj_settings.feasibility_run = false; + fj_settings.iteration_limit = iter_limit; + fj_settings.load_balancing_mode = detail::fj_load_balancing_mode_t::ALWAYS_ON; + fj_settings.seed = cuopt::seed_generator::get_seed(); + + auto state = run_fj_instance(test_instance, fj_settings); + auto& solution = state.solution; -INSTANTIATE_TEST_SUITE_P( - MIPSolveTest, - MIPSolveParametricTest, - testing::Values( - // Bug: https://github.com/NVIDIA/cuopt/issues/214 - // std::make_tuple("50v-10.mps", 7800, 100000), - // std::make_tuple("fiball.mps", 140, 25000), - // std::make_tuple("rmatr200-p5.mps", 7000, 10000), - std::make_tuple("gen-ip054.mps", 7500, 20000), - std::make_tuple("sct2.mps", 100, 50000), - std::make_tuple("uccase9.mps", 4000000, 50000), - // unstable, prone to failure on slight weight changes - // std::make_tuple("drayage-25-23.mps", 300000, 50000), - std::make_tuple("tr12-30.mps", 300000, 50000), - std::make_tuple("neos-3004026-krka.mps", - +std::numeric_limits::infinity(), - 35000), // feasibility - // std::make_tuple("nursesched-medium-hint03.mps", 12000, 50000), // too large - std::make_tuple("ns1208400.mps", 2, 60000), - std::make_tuple("gmu-35-50.mps", -2300000, 25000), - std::make_tuple("n2seq36q.mps", 158800, 25000), - std::make_tuple("seymour1.mps", 440, 50000), - std::make_tuple("cvs16r128-89.mps", -50, 10000) -// TEMPORARY: occasional cusparse transpose issues on ARM in CI -#ifndef __aarch64__ - , - std::make_tuple("thor50dday.mps", 250000, 1000) -#endif - )); - -TEST(mip_solve, feasibility_jump_feas_test) -{ - for (const auto& instance : {"tr12-30.mps", - "sct2.mps" -#ifndef __aarch64__ - , - "thor50dday.mps" -#endif - }) { - run_fj_check_feasible(instance); + printf("%s[seed=%x]: Solution generated with FJ: is_feasible %d, objective %g (raw %g)", + test_instance.c_str(), + fj_settings.seed, + solution.get_feasible(), + solution.get_user_objective(), + solution.get_objective()); + + static std::unordered_map first_val_map; + if (first_val_map.count(test_instance) == 0) { + first_val_map[test_instance] = solution.get_user_objective(); } + EXPECT_NEAR(solution.get_user_objective(), first_val_map[test_instance], 1.0) + << test_instance << " determinism objective mismatch"; + + return true; } -TEST(mip_solve, feasibility_jump_obj_runoff_test) +// class MIPSolveParametricTest : public testing::TestWithParam> { +// }; + +// TEST_P(MIPSolveParametricTest, feasibility_jump_obj_test) +// { +// auto [instance, obj_target, iter_limit] = GetParam(); +// EXPECT_TRUE(run_fj_check_objective(instance, iter_limit, obj_target)); +// } + +// INSTANTIATE_TEST_SUITE_P( +// MIPSolveTest, +// MIPSolveParametricTest, +// testing::Values( +// // Bug: https://github.com/NVIDIA/cuopt/issues/214 +// // std::make_tuple("50v-10.mps", 7800, 100000), +// // std::make_tuple("fiball.mps", 140, 25000), +// // std::make_tuple("rmatr200-p5.mps", 7000, 10000), +// std::make_tuple("gen-ip054.mps", 7500, 20000), +// std::make_tuple("sct2.mps", 100, 50000), +// std::make_tuple("uccase9.mps", 4000000, 50000), +// // unstable, prone to failure on slight weight changes +// // std::make_tuple("drayage-25-23.mps", 300000, 50000), +// std::make_tuple("tr12-30.mps", 300000, 50000), +// std::make_tuple("neos-3004026-krka.mps", +// +std::numeric_limits::infinity(), +// 35000), // feasibility +// // std::make_tuple("nursesched-medium-hint03.mps", 12000, 50000), // too large +// std::make_tuple("ns1208400.mps", 2, 60000), +// std::make_tuple("gmu-35-50.mps", -2300000, 25000), +// std::make_tuple("n2seq36q.mps", 158800, 25000), +// std::make_tuple("seymour1.mps", 440, 50000), +// std::make_tuple("cvs16r128-89.mps", -50, 10000) +// // TEMPORARY: occasional cusparse transpose issues on ARM in CI +// #ifndef __aarch64__ +// , +// std::make_tuple("thor50dday.mps", 250000, 1000) +// #endif +// )); + +// TEST(mip_solve, feasibility_jump_feas_test) +// { +// for (const auto& instance : {"tr12-30.mps", +// "sct2.mps" +// #ifndef __aarch64__ +// , +// "thor50dday.mps" +// #endif +// }) { +// run_fj_check_feasible(instance); +// } +// } + +// TEST(mip_solve, feasibility_jump_obj_runoff_test) +// { +// for (const auto& instance : {"minrep_inf.mps", "sct2.mps", "uccase9.mps", +// /*"buildingenergy.mps"*/}) { +// run_fj_check_no_obj_runoff(instance); +// } +// } + +TEST(mip_solve, feasibility_jump_determinism) { - for (const auto& instance : {"minrep_inf.mps", "sct2.mps", "uccase9.mps", - /*"buildingenergy.mps"*/}) { - run_fj_check_no_obj_runoff(instance); + int seed = + std::getenv("CUOPT_SEED") ? std::stoi(std::getenv("CUOPT_SEED")) : std::random_device{}(); + + for (const auto& [instance, iter_limit] : {std::make_pair("thor50dday.mps", 1000), + std::make_pair("gen-ip054.mps", 1000), + std::make_pair("50v-10.mps", 1000), + std::make_pair("seymour1.mps", 1000), + std::make_pair("rmatr200-p5.mps", 1000), + std::make_pair("tr12-30.mps", 1000), + std::make_pair("sct2.mps", 1000), + std::make_pair("uccase9.mps", 1000), + std::make_pair("supportcase42.mps", 25000)}) { + for (int i = 0; i < 10; i++) { + // while (true) { + cuopt::seed_generator::set_seed(seed); + run_fj_check_determinism(instance, iter_limit); + } } } diff --git a/cpp/tests/mip/load_balancing_test.cu b/cpp/tests/mip/load_balancing_test.cu index 5e2f08007d..11e518e892 100644 --- a/cpp/tests/mip/load_balancing_test.cu +++ b/cpp/tests/mip/load_balancing_test.cu @@ -9,6 +9,7 @@ #include "mip_utils.cuh" #include +#include #include #include #include @@ -129,6 +130,7 @@ void test_multi_probe(std::string path) detail::problem_t problem(op_problem); mip_solver_settings_t default_settings{}; detail::pdhg_solver_t pdhg_solver(problem.handle_ptr, problem); + pdlp_hyper_params::pdlp_hyper_params_t hyper_params{}; detail::pdlp_initial_scaling_strategy_t scaling(&handle_, problem, 10, @@ -137,8 +139,10 @@ void test_multi_probe(std::string path) problem.reverse_offsets, problem.reverse_constraints, nullptr, + hyper_params, true); - detail::mip_solver_t solver(problem, default_settings, scaling, cuopt::timer_t(0)); + auto timer = cuopt::termination_checker_t(0.0, cuopt::termination_checker_t::root_tag_t{}); + detail::mip_solver_t solver(problem, default_settings, scaling, timer); detail::load_balanced_problem_t lb_problem(problem); detail::load_balanced_bounds_presolve_t lb_prs(lb_problem, solver.context); diff --git a/cpp/tests/mip/local_search_test.cu b/cpp/tests/mip/local_search_test.cu new file mode 100644 index 0000000000..b5ef19d58a --- /dev/null +++ b/cpp/tests/mip/local_search_test.cu @@ -0,0 +1,264 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../linear_programming/utilities/pdlp_test_utilities.cuh" +#include "determinism_utils.cuh" +#include "mip_utils.cuh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace cuopt::linear_programming::test { + +void init_handler(const raft::handle_t* handle_ptr) +{ + // Init cuBlas / cuSparse context here to avoid having it during solving time + RAFT_CUBLAS_TRY(raft::linalg::detail::cublassetpointermode( + handle_ptr->get_cublas_handle(), CUBLAS_POINTER_MODE_DEVICE, handle_ptr->get_stream())); + RAFT_CUSPARSE_TRY(raft::sparse::detail::cusparsesetpointermode( + handle_ptr->get_cusparse_handle(), CUSPARSE_POINTER_MODE_DEVICE, handle_ptr->get_stream())); +} + +static void setup_device_symbols(rmm::cuda_stream_view stream_view) { (void)stream_view; } + +enum local_search_mode_t { + FP = 0, + STAGED_FP, + FJ_LINE_SEGMENT, + FJ_ON_ZERO, + FJ_ANNEALING, +}; + +// Helper function to setup MIP solver and run FJ with given settings and initial solution +static uint32_t run_fp(std::string test_instance, local_search_mode_t mode) +{ + const raft::handle_t handle_{}; + std::cout << "Running: " << test_instance << std::endl; + + auto path = cuopt::test::get_rapids_dataset_root_dir() + ("/mip/" + test_instance); + cuopt::mps_parser::mps_data_model_t mps_problem = + cuopt::mps_parser::parse_mps(path, false); + handle_.sync_stream(); + auto op_problem = mps_data_model_to_optimization_problem(&handle_, mps_problem); + problem_checking_t::check_problem_representation(op_problem); + + init_handler(op_problem.get_handle_ptr()); + // run the problem constructor of MIP, so that we do bounds standardization + auto settings = mip_solver_settings_t{}; + settings.time_limit = 120.; + settings.determinism_mode = CUOPT_MODE_DETERMINISTIC; + + detail::problem_t problem(op_problem, settings.get_tolerances(), true); + problem.preprocess_problem(); + + setup_device_symbols(op_problem.get_handle_ptr()->get_stream()); + + pdlp_hyper_params::pdlp_hyper_params_t hyper_params{}; + detail::pdlp_initial_scaling_strategy_t scaling(&handle_, + problem, + 10, + 1.0, + problem.reverse_coefficients, + problem.reverse_offsets, + problem.reverse_constraints, + nullptr, + hyper_params, + true); + auto timer = + cuopt::termination_checker_t(settings.time_limit, cuopt::termination_checker_t::root_tag_t{}); + detail::mip_solver_t solver(problem, settings, scaling, timer); + problem.tolerances = settings.get_tolerances(); + + rmm::device_uvector lp_optimal_solution(problem.n_variables, + problem.handle_ptr->get_stream()); + thrust::fill(problem.handle_ptr->get_thrust_policy(), + lp_optimal_solution.begin(), + lp_optimal_solution.end(), + 0.0); + detail::lp_state_t& lp_state = problem.lp_state; + // resize because some constructor might be called before the presolve + lp_state.resize(problem, problem.handle_ptr->get_stream()); + detail::relaxed_lp_settings_t lp_settings{}; + lp_settings.time_limit = std::numeric_limits::max(); + lp_settings.tolerance = 1e-6; + lp_settings.return_first_feasible = false; + lp_settings.save_state = false; + // lp_settings.iteration_limit = 5; + auto lp_result = + detail::get_relaxed_lp_solution(problem, lp_optimal_solution, lp_state, lp_settings); + EXPECT_EQ(lp_result.get_termination_status(), pdlp_termination_status_t::Optimal); + clamp_within_var_bounds(lp_optimal_solution, &problem, problem.handle_ptr); + + // return detail::compute_hash(lp_optimal_solution); + + detail::local_search_t local_search(solver.context, lp_optimal_solution); + + detail::solution_t solution(problem); + solution.assign_random_within_bounds(); + solution.compute_feasibility(); + + printf("Model fingerprint: 0x%x\n", problem.get_fingerprint()); + printf("LP optimal hash: 0x%x\n", + detail::compute_hash(make_span(lp_optimal_solution), problem.handle_ptr->get_stream())); + printf("running mode: %d\n", mode); + + work_limit_context_t work_limit_context("LocalSearch"); + work_limit_context.deterministic = true; + local_search.fp.timer = work_limit_timer_t(work_limit_context, 10, timer); + + detail::ls_config_t ls_config{}; + + if (mode == local_search_mode_t::FP) { + bool is_feasible = false; + int iterations = 0; + while (true) { + is_feasible = local_search.fp.run_single_fp_descent(solution); + printf("fp_loop it %d, is_feasible %d\n", iterations, is_feasible); + if (is_feasible) { + break; + } else { + is_feasible = local_search.fp.restart_fp(solution); + if (is_feasible) { break; } + } + iterations++; + } + } else if (mode == local_search_mode_t::FJ_LINE_SEGMENT) { + local_search.run_fj_line_segment( + solution, work_limit_timer_t(work_limit_context, 6000, timer), ls_config); + } else if (mode == local_search_mode_t::FJ_ON_ZERO) { + local_search.run_fj_on_zero(solution, work_limit_timer_t(work_limit_context, 6000, timer)); + } else if (mode == local_search_mode_t::FJ_ANNEALING) { + local_search.run_fj_annealing( + solution, work_limit_timer_t(work_limit_context, 6000, timer), ls_config); + } + + std::vector hashes; + hashes.push_back(detail::compute_hash(solution.get_host_assignment())); + printf("hashes: 0x%x, hash of the hash: 0x%x\n", hashes[0], detail::compute_hash(hashes)); + + return detail::compute_hash(hashes); + // return {host_copy(solution_vector, problem.handle_ptr->get_stream()), iterations}; +} + +static uint32_t run_fp_check_determinism(std::string test_instance, + local_search_mode_t mode, + unsigned long seed) +{ + cuopt::seed_generator::set_seed(seed); + + return run_fp(test_instance, mode); + + // auto state = run_fp(test_instance, fj_settings); + // auto& solution = state.solution; + + // CUOPT_LOG_DEBUG("%s: Solution generated with FJ: is_feasible %d, objective %g (raw %g)", + // test_instance.c_str(), + // solution.get_feasible(), + // solution.get_user_objective(), + // solution.get_objective()); + + // static auto first_val = solution.get_user_objective(); +} + +class LocalSearchTestParams : public testing::TestWithParam> {}; + +TEST_P(LocalSearchTestParams, local_search_operator_determinism) +{ + cuopt::init_logger_t log("", true); + cuopt::default_logger().set_pattern("[%n] [%-6l] %v"); + cuopt::default_logger().set_level(rapids_logger::level_enum::debug); + cuopt::default_logger().flush_on(rapids_logger::level_enum::debug); + + spin_stream_raii_t spin_stream_1; + spin_stream_raii_t spin_stream_2; + + auto mode = std::get<0>(GetParam()); + + for (const auto& instance : { + //"thor50dday.mps", + "gen-ip054.mps", + "50v-10.mps", + "seymour1.mps", + "rmatr200-p5.mps", + "tr12-30.mps", + //"sct2.mps", + //"uccase9.mps" + }) { + // for (int i = 0; i < 10; i++) + // while (true) { + // run_fp_check_determinism(instance, 1000); + // } + + unsigned long seed = std::getenv("CUOPT_SEED") + ? (unsigned long)std::stoi(std::getenv("CUOPT_SEED")) + : (unsigned long)std::random_device{}(); + std::cerr << "Tested with seed " << seed << "\n"; + uint32_t gold_hash = 0; + for (int i = 0; i < 5; ++i) { + uint32_t hash = run_fp_check_determinism(instance, mode, seed); + if (i == 0) { + gold_hash = hash; + printf("Gold hash: 0x%x\n", gold_hash); + } else { + ASSERT_EQ(hash, gold_hash); + printf("Hash: 0x%x\n", hash); + } + } + } +} + +INSTANTIATE_TEST_SUITE_P(LocalSearchTests, + LocalSearchTestParams, + testing::Values( + // std::make_tuple(local_search_mode_t::FP) + // std::make_tuple(local_search_mode_t::FJ_LINE_SEGMENT), + // std::make_tuple(local_search_mode_t::FJ_ON_ZERO), + std::make_tuple(local_search_mode_t::FJ_ANNEALING))); + +} // namespace cuopt::linear_programming::test diff --git a/cpp/tests/mip/mip_utils.cuh b/cpp/tests/mip/mip_utils.cuh index 5c2b39d290..b0af0fecf8 100644 --- a/cpp/tests/mip/mip_utils.cuh +++ b/cpp/tests/mip/mip_utils.cuh @@ -8,9 +8,14 @@ #include #include #include +#include #include +#include +#include #include +#include #include +#include namespace cuopt::linear_programming::test { @@ -180,4 +185,66 @@ static std::tuple test_mps_file( solution.get_solution_bound()); } +struct fj_tweaks_t { + double objective_weight = 0; +}; + +struct fj_state_t { + detail::solution_t solution; + std::vector solution_vector; + int minimums; + double incumbent_objective; + double incumbent_violation; +}; + +static fj_state_t run_fj(detail::problem_t& problem, + const detail::fj_settings_t& fj_settings, + fj_tweaks_t tweaks = {}, + std::vector initial_solution = {}, + int determinism_mode = CUOPT_MODE_OPPORTUNISTIC) +{ + pdlp_hyper_params::pdlp_hyper_params_t hyper_params{}; + detail::pdlp_initial_scaling_strategy_t scaling(problem.handle_ptr, + problem, + 10, + 1.0, + problem.reverse_coefficients, + problem.reverse_offsets, + problem.reverse_constraints, + nullptr, + hyper_params, + true); + + auto settings = mip_solver_settings_t{}; + settings.time_limit = 30.; + settings.determinism_mode = determinism_mode; + auto timer = cuopt::termination_checker_t(30.0, cuopt::termination_checker_t::root_tag_t{}); + detail::mip_solver_t solver(problem, settings, scaling, timer); + + detail::solution_t solution(*solver.context.problem_ptr); + if (initial_solution.size() > 0) { + expand_device_copy(solution.assignment, initial_solution, solution.handle_ptr->get_stream()); + } else { + thrust::fill(solution.handle_ptr->get_thrust_policy(), + solution.assignment.begin(), + solution.assignment.end(), + 0.0); + } + solution.clamp_within_bounds(); + + detail::fj_t fj(solver.context, fj_settings); + fj.reset_weights(solution.handle_ptr->get_stream(), 1.); + fj.objective_weight.set_value_async(tweaks.objective_weight, solution.handle_ptr->get_stream()); + solution.handle_ptr->sync_stream(); + + fj.solve(solution); + auto solution_vector = host_copy(solution.assignment, solution.handle_ptr->get_stream()); + + return {solution, + solution_vector, + fj.climbers[0]->local_minimums_reached.value(solution.handle_ptr->get_stream()), + fj.climbers[0]->incumbent_objective.value(solution.handle_ptr->get_stream()), + fj.climbers[0]->violation_score.value(solution.handle_ptr->get_stream())}; +} + } // namespace cuopt::linear_programming::test diff --git a/cpp/tests/mip/miplib_test.cu b/cpp/tests/mip/miplib_test.cu index 7607ad91f8..ac4312e04b 100644 --- a/cpp/tests/mip/miplib_test.cu +++ b/cpp/tests/mip/miplib_test.cu @@ -33,7 +33,9 @@ struct result_map_t { double cost; }; -void test_miplib_file(result_map_t test_instance, mip_solver_settings_t settings) +void test_miplib_file(result_map_t test_instance, + mip_solver_settings_t settings, + bool heuristic = false) { const raft::handle_t handle_{}; @@ -49,6 +51,7 @@ void test_miplib_file(result_map_t test_instance, mip_solver_settings_t solution = solve_mip(&handle_, problem, settings); bool is_feasible = solution.get_termination_status() == mip_termination_status_t::FeasibleFound || solution.get_termination_status() == mip_termination_status_t::Optimal; @@ -70,4 +73,13 @@ TEST(mip_solve, run_small_tests) } } +// TEST(mip_solve, run_small_tests_determinism) +// { +// std::vector test_instances = { +// {"mip/50v-10.mps", 11311031.}, {"mip/neos5.mps", 15.}, {"mip/swath1.mps", 1300.}}; +// for (const auto& test_instance : test_instances) { +// test_miplib_file(test_instance, true); +// } +// } + } // namespace cuopt::linear_programming::test diff --git a/cpp/tests/mip/multi_probe_test.cu b/cpp/tests/mip/multi_probe_test.cu index 073c153486..b50fb2848c 100644 --- a/cpp/tests/mip/multi_probe_test.cu +++ b/cpp/tests/mip/multi_probe_test.cu @@ -6,6 +6,7 @@ /* clang-format on */ #include "../linear_programming/utilities/pdlp_test_utilities.cuh" +#include "determinism_utils.cuh" #include "mip_utils.cuh" #include @@ -43,9 +44,10 @@ void init_handler(const raft::handle_t* handle_ptr) } std::tuple, std::vector, std::vector> select_k_random( - detail::problem_t& problem, int sample_size) + detail::problem_t& problem, + int sample_size, + unsigned long seed = std::random_device{}()) { - auto seed = std::random_device{}(); std::cerr << "Tested with seed " << seed << "\n"; problem.compute_n_integer_vars(); auto [v_lb, v_ub] = extract_host_bounds(problem.variable_bounds, problem.handle_ptr); @@ -138,10 +140,8 @@ multi_probe_results( std::move(h_lb_0), std::move(h_ub_0), std::move(h_lb_1), std::move(h_ub_1)); } -void test_multi_probe(std::string path) +uint32_t test_multi_probe(std::string path, unsigned long seed = std::random_device{}()) { - auto memory_resource = make_async(); - rmm::mr::set_current_device_resource(memory_resource.get()); const raft::handle_t handle_{}; cuopt::mps_parser::mps_data_model_t mps_problem = cuopt::mps_parser::parse_mps(path, false); @@ -161,12 +161,13 @@ void test_multi_probe(std::string path) nullptr, hyper_params, true); - detail::mip_solver_t solver(problem, default_settings, scaling, cuopt::timer_t(0)); + auto timer = cuopt::termination_checker_t(0.0, cuopt::termination_checker_t::root_tag_t{}); + detail::mip_solver_t solver(problem, default_settings, scaling, timer); detail::bound_presolve_t bnd_prb_0(solver.context); detail::bound_presolve_t bnd_prb_1(solver.context); detail::multi_probe_t multi_probe_prs(solver.context); - auto probe_tuple = select_k_random(problem, 100); + auto probe_tuple = select_k_random(problem, 100, seed); auto bounds_probe_vals = convert_probe_tuple(probe_tuple); auto [bnd_lb_0, bnd_ub_0, bnd_lb_1, bnd_ub_1] = @@ -185,6 +186,16 @@ void test_multi_probe(std::string path) auto mlp_min_act_1 = host_copy(multi_probe_prs.upd_1.min_activity, stream); auto mlp_max_act_1 = host_copy(multi_probe_prs.upd_1.max_activity, stream); + std::vector hashes; + hashes.push_back(detail::compute_hash(bnd_min_act_0)); + hashes.push_back(detail::compute_hash(bnd_min_act_1)); + hashes.push_back(detail::compute_hash(bnd_max_act_0)); + hashes.push_back(detail::compute_hash(bnd_max_act_1)); + hashes.push_back(detail::compute_hash(bnd_lb_0)); + hashes.push_back(detail::compute_hash(bnd_ub_0)); + hashes.push_back(detail::compute_hash(bnd_lb_1)); + hashes.push_back(detail::compute_hash(bnd_ub_1)); + for (int i = 0; i < (int)bnd_min_act_0.size(); ++i) { EXPECT_DOUBLE_EQ(bnd_min_act_0[i], mlp_min_act_0[i]); EXPECT_DOUBLE_EQ(bnd_max_act_0[i], mlp_max_act_0[i]); @@ -198,6 +209,9 @@ void test_multi_probe(std::string path) EXPECT_DOUBLE_EQ(bnd_lb_1[i], m_lb_1[i]); EXPECT_DOUBLE_EQ(bnd_ub_1[i], m_ub_1[i]); } + + // return a composite hash of all the hashes to check for determinism + return detail::compute_hash(hashes); } TEST(presolve, multi_probe) @@ -211,4 +225,29 @@ TEST(presolve, multi_probe) } } +TEST(presolve, multi_probe_deterministic) +{ + spin_stream_raii_t spin_stream_1; + + std::vector test_instances = { + "mip/50v-10-free-bound.mps", + "mip/neos5-free-bound.mps", + "mip/neos5.mps", + "mip/50v-10.mps", + }; + for (const auto& test_instance : test_instances) { + std::cout << "Running: " << test_instance << std::endl; + unsigned long seed = std::random_device{}(); + auto path = make_path_absolute(test_instance); + uint32_t gold_hash = 0; + for (int i = 0; i < 10; ++i) { + auto hash = test_multi_probe(path, seed); + if (i == 0) { + gold_hash = hash; + } else { + EXPECT_EQ(hash, gold_hash); + } + } + } +} } // namespace cuopt::linear_programming::test diff --git a/cpp/tests/mip/presolve_test.cu b/cpp/tests/mip/presolve_test.cu index cf8abd1b69..2fa1cf9dc4 100644 --- a/cpp/tests/mip/presolve_test.cu +++ b/cpp/tests/mip/presolve_test.cu @@ -6,12 +6,22 @@ /* clang-format on */ #include "../linear_programming/utilities/pdlp_test_utilities.cuh" +#include "determinism_utils.cuh" +#include "mip_utils.cuh" +#include +#include #include +#include +#include #include +#include #include +#include #include #include +#include +#include #include #include #include @@ -29,6 +39,184 @@ namespace cuopt::linear_programming::test { +void init_handler(const raft::handle_t* handle_ptr) +{ + // Init cuBlas / cuSparse context here to avoid having it during solving time + RAFT_CUBLAS_TRY(raft::linalg::detail::cublassetpointermode( + handle_ptr->get_cublas_handle(), CUBLAS_POINTER_MODE_DEVICE, handle_ptr->get_stream())); + RAFT_CUSPARSE_TRY(raft::sparse::detail::cusparsesetpointermode( + handle_ptr->get_cusparse_handle(), CUSPARSE_POINTER_MODE_DEVICE, handle_ptr->get_stream())); +} + +std::tuple, std::vector, std::vector> select_k_random( + detail::problem_t& problem, + int sample_size, + unsigned long seed = std::random_device{}()) +{ + std::cerr << "Tested with seed " << seed << "\n"; + problem.compute_n_integer_vars(); + auto [v_lb, v_ub] = extract_host_bounds(problem.variable_bounds, problem.handle_ptr); + auto int_var_id = host_copy(problem.integer_indices, problem.handle_ptr->get_stream()); + int_var_id.erase( + std::remove_if(int_var_id.begin(), + int_var_id.end(), + [v_lb_sp = v_lb, v_ub_sp = v_ub](auto id) { + return !(std::isfinite(v_lb_sp[id]) && std::isfinite(v_ub_sp[id])); + }), + int_var_id.end()); + sample_size = std::min(sample_size, static_cast(int_var_id.size())); + std::vector random_int_vars; + std::mt19937 m{seed}; + std::sample( + int_var_id.begin(), int_var_id.end(), std::back_inserter(random_int_vars), sample_size, m); + std::vector probe_0(sample_size); + std::vector probe_1(sample_size); + for (int i = 0; i < static_cast(random_int_vars.size()); ++i) { + if (i % 2) { + probe_0[i] = v_lb[random_int_vars[i]]; + probe_1[i] = v_ub[random_int_vars[i]]; + } else { + probe_1[i] = v_lb[random_int_vars[i]]; + probe_0[i] = v_ub[random_int_vars[i]]; + } + } + return std::make_tuple(std::move(random_int_vars), std::move(probe_0), std::move(probe_1)); +} + +std::pair>, std::vector>> +convert_probe_tuple(std::tuple, std::vector, std::vector>& probe) +{ + std::vector> probe_first; + std::vector> probe_second; + for (size_t i = 0; i < std::get<0>(probe).size(); ++i) { + probe_first.emplace_back(thrust::make_pair(std::get<0>(probe)[i], std::get<1>(probe)[i])); + probe_second.emplace_back(thrust::make_pair(std::get<0>(probe)[i], std::get<2>(probe)[i])); + } + return std::make_pair(std::move(probe_first), std::move(probe_second)); +} + +uint32_t test_probing_cache_determinism(std::string path, + unsigned long seed = std::random_device{}()) +{ + const raft::handle_t handle_{}; + cuopt::mps_parser::mps_data_model_t mps_problem = + cuopt::mps_parser::parse_mps(path, false); + handle_.sync_stream(); + auto op_problem = mps_data_model_to_optimization_problem(&handle_, mps_problem); + problem_checking_t::check_problem_representation(op_problem); + detail::problem_t problem(op_problem); + mip_solver_settings_t default_settings{}; + default_settings.mip_scaling = false; // we're not checking scaling determinism here + pdlp_hyper_params::pdlp_hyper_params_t hyper_params{}; + detail::pdlp_initial_scaling_strategy_t scaling(&handle_, + problem, + 10, + 1.0, + problem.reverse_coefficients, + problem.reverse_offsets, + problem.reverse_constraints, + nullptr, + hyper_params, + true); + auto timer = cuopt::termination_checker_t(0.0, cuopt::termination_checker_t::root_tag_t{}); + detail::mip_solver_t solver(problem, default_settings, scaling, timer); + detail::bound_presolve_t bnd_prb(solver.context); + + work_limit_context_t work_limit_context("ProbingCache"); + // rely on the iteration limit + compute_probing_cache( + bnd_prb, + problem, + work_limit_timer_t(work_limit_context, std::numeric_limits::max(), timer)); + std::vector, 2>>> cached_values( + bnd_prb.probing_cache.probing_cache.begin(), bnd_prb.probing_cache.probing_cache.end()); + std::sort(cached_values.begin(), cached_values.end(), [](const auto& a, const auto& b) { + return a.first < b.first; + }); + + std::vector probed_indices; + std::vector intervals; + std::vector interval_types; + + std::vector var_to_cached_bound_keys; + std::vector var_to_cached_bound_lb; + std::vector var_to_cached_bound_ub; + for (const auto& a : cached_values) { + probed_indices.push_back(a.first); + intervals.push_back(a.second[0].val_interval.val); + intervals.push_back(a.second[1].val_interval.val); + interval_types.push_back(a.second[0].val_interval.interval_type); + interval_types.push_back(a.second[1].val_interval.interval_type); + + auto sorted_map = std::map>( + a.second[0].var_to_cached_bound_map.begin(), a.second[0].var_to_cached_bound_map.end()); + for (const auto& [var_id, cached_bound] : sorted_map) { + var_to_cached_bound_keys.push_back(var_id); + var_to_cached_bound_lb.push_back(cached_bound.lb); + var_to_cached_bound_ub.push_back(cached_bound.ub); + } + } + + std::vector hashes; + hashes.push_back(detail::compute_hash(probed_indices)); + hashes.push_back(detail::compute_hash(intervals)); + hashes.push_back(detail::compute_hash(interval_types)); + hashes.push_back(detail::compute_hash(var_to_cached_bound_keys)); + hashes.push_back(detail::compute_hash(var_to_cached_bound_lb)); + hashes.push_back(detail::compute_hash(var_to_cached_bound_ub)); + + // return a composite hash of all the hashes to check for determinism + return detail::compute_hash(hashes); +} + +uint32_t test_scaling_determinism(std::string path, unsigned long seed = std::random_device{}()) +{ + const raft::handle_t handle_{}; + cuopt::mps_parser::mps_data_model_t mps_problem = + cuopt::mps_parser::parse_mps(path, false); + handle_.sync_stream(); + auto op_problem = mps_data_model_to_optimization_problem(&handle_, mps_problem); + problem_checking_t::check_problem_representation(op_problem); + detail::problem_t problem(op_problem); + + pdlp_hyper_params::pdlp_hyper_params_t hyper_params{}; + hyper_params.update_primal_weight_on_initial_solution = false; + hyper_params.update_step_size_on_initial_solution = true; + // problem contains unpreprocessed data + detail::problem_t scaled_problem(problem); + + detail::pdlp_initial_scaling_strategy_t scaling( + scaled_problem.handle_ptr, + scaled_problem, + hyper_params.default_l_inf_ruiz_iterations, + (double)hyper_params.default_alpha_pock_chambolle_rescaling, + scaled_problem.reverse_coefficients, + scaled_problem.reverse_offsets, + scaled_problem.reverse_constraints, + nullptr, + hyper_params, + true); + + scaling.scale_problem(); + + // generate a random initial solution in order to ensure scaling of solution vectors is + // deterministic as well as the initial step size + std::vector initial_solution(scaled_problem.n_variables); + std::mt19937 m{seed}; + std::generate(initial_solution.begin(), initial_solution.end(), [&m]() { return m(); }); + auto d_initial_solution = device_copy(initial_solution, handle_.get_stream()); + scaling.scale_primal(d_initial_solution); + + scaled_problem.preprocess_problem(); + + detail::trivial_presolve(scaled_problem); + + std::vector hashes; + hashes.push_back(detail::compute_hash(d_initial_solution, handle_.get_stream())); + hashes.push_back(scaled_problem.get_fingerprint()); + return detail::compute_hash(hashes); +} + TEST(problem, find_implied_integers) { const raft::handle_t handle_{}; @@ -62,4 +250,63 @@ TEST(problem, find_implied_integers) ((int)detail::problem_t::var_flags_t::VAR_IMPLIED_INTEGER)); } +TEST(presolve, probing_cache_deterministic) +{ + spin_stream_raii_t spin_stream_1; + + std::vector test_instances = {"mip/50v-10-free-bound.mps", + "mip/neos5-free-bound.mps", + "mip/neos5.mps", + "mip/50v-10.mps", + "mip/gen-ip054.mps", + "mip/rmatr200-p5.mps"}; + for (const auto& test_instance : test_instances) { + std::cout << "Running: " << test_instance << std::endl; + unsigned long seed = std::random_device{}(); + std::cerr << "Tested with seed " << seed << "\n"; + auto path = make_path_absolute(test_instance); + uint32_t gold_hash = 0; + for (int i = 0; i < 10; ++i) { + auto hash = test_probing_cache_determinism(path, seed); + if (i == 0) { + gold_hash = hash; + std::cout << "Gold hash: " << gold_hash << std::endl; + } else { + EXPECT_EQ(hash, gold_hash); + } + } + } +} + +TEST(presolve, mip_scaling_deterministic) +{ + spin_stream_raii_t spin_stream_1; + spin_stream_raii_t spin_stream_2; + + std::vector test_instances = {"mip/sct2.mps", + "mip/thor50dday.mps", + "mip/uccase9.mps", + "mip/neos5-free-bound.mps", + "mip/neos5.mps", + "mip/50v-10.mps", + "mip/gen-ip054.mps", + "mip/rmatr200-p5.mps"}; + for (const auto& test_instance : test_instances) { + std::cout << "Running: " << test_instance << std::endl; + unsigned long seed = std::random_device{}(); + std::cerr << "Tested with seed " << seed << "\n"; + auto path = make_path_absolute(test_instance); + uint32_t gold_hash = 0; + for (int i = 0; i < 10; ++i) { + auto hash = test_scaling_determinism(path, seed); + if (i == 0) { + gold_hash = hash; + std::cout << "Gold hash: " << gold_hash << std::endl; + } else { + EXPECT_EQ(hash, gold_hash); + } + } + } +} + } // namespace cuopt::linear_programming::test diff --git a/cpp/tests/mip/unit_test.cu b/cpp/tests/mip/unit_test.cu index 29b58b736b..9c6d5a3c09 100644 --- a/cpp/tests/mip/unit_test.cu +++ b/cpp/tests/mip/unit_test.cu @@ -283,4 +283,25 @@ INSTANTIATE_TEST_SUITE_P( std::make_tuple( false, false, false, cuopt::linear_programming::mip_termination_status_t::Optimal))); +// TEST_P(MILPTestParams, TestDeterminism) +// { +// bool maximize = std::get<0>(GetParam()); +// bool scaling = std::get<1>(GetParam()); +// bool heuristics_only = std::get<2>(GetParam()); +// auto expected_termination_status = std::get<3>(GetParam()); + +// raft::handle_t handle; +// auto problem = create_std_milp_problem(maximize); + +// cuopt::linear_programming::mip_solver_settings_t settings{}; +// settings.mip_scaling = true; +// settings.heuristics_only = true; +// settings.presolve = true; +// settings.deterministic = true; + +// auto result = cuopt::linear_programming::solve_mip(&handle, problem, settings); + +// EXPECT_EQ(result.get_termination_status(), expected_termination_status); +// } + } // namespace cuopt::linear_programming::test diff --git a/datasets/mip/download_miplib_test_dataset.sh b/datasets/mip/download_miplib_test_dataset.sh index 3040f0f543..f8df265618 100755 --- a/datasets/mip/download_miplib_test_dataset.sh +++ b/datasets/mip/download_miplib_test_dataset.sh @@ -25,6 +25,7 @@ INSTANCES=( "enlight_hard" "enlight11" "supportcase22" + "supportcase42" ) BASE_URL="https://miplib.zib.de/WebData/instances"