Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cpp/src/branch_and_bound/branch_and_bound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2507,6 +2507,7 @@ void branch_and_bound_t<i_t, f_t>::solve_submip(diving_worker_t<i_t, f_t>* worke
f_t work_limit = 1.0;
submip_fj_cpu_worker.create_worker(submip_bnb.original_lp_,
submip_bnb.var_types_,
submip_bnb.original_problem_.num_cols,
initial_guess,
submip_bnb.settings_,
std::format("{} [CPU FJ]", log_prefix),
Expand Down Expand Up @@ -2984,6 +2985,7 @@ void branch_and_bound_t<i_t, f_t>::recursive_submip(
submip_fj_cpu_worker.create_worker(
worker->leaf_problem,
worker->var_types,
original_problem_.num_cols,
worker->leaf_solution.x,
settings_,
std::format("{} [CPU FJ]", submip_settings.log.log_prefix),
Expand Down Expand Up @@ -3063,7 +3065,7 @@ void branch_and_bound_t<i_t, f_t>::launch_root_heuristics(
set_solution_from_cpu_fj(obj, assignment, work_units);
};
current_heuristic->fj_cpu_worker_.create_worker(
lp, var_types_, lp_solution.x, settings_, "[RootCut CPUFJ] ");
lp, var_types_, original_problem_.num_cols, lp_solution.x, settings_, "[RootCut CPUFJ] ");
++(*worker_count);
++current_heuristic->active_workers_;

Expand Down
9 changes: 8 additions & 1 deletion cpp/src/mip_heuristics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ set(MIP_NON_LP_FILES
${CMAKE_CURRENT_SOURCE_DIR}/presolve/trivial_presolve.cu
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/feasibility_jump.cu
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/feasibility_jump_kernels.cu
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/fj_cpu.cu
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/cpu/audit.cpp
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/cpu/climber.cpp
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/cpu/loop.cpp
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/cpu/portfolio.cpp
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/cpu/search/escape.cpp
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/cpu/setup/lp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/cpu/setup/structure.cpp
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/fj_cpu_bridge.cu
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/fj_cpu_binary.cu
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/fj_cpu_binary_preprocess.cu
${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/fj_cpu_binary_kernels.cpp
Expand Down
15 changes: 9 additions & 6 deletions cpp/src/mip_heuristics/diversity/diversity_manager.cu
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <utilities/copy_helpers.hpp>
#include <utilities/scope_guard.hpp>

#include <algorithm>
#include <chrono>
#include <cmath>
#include <limits>
Expand Down Expand Up @@ -197,6 +198,7 @@ void diversity_manager_t<i_t, f_t>::add_user_given_solutions(
const bool has_papilo = problem_ptr->has_papilo_presolve_data();
const i_t papilo_orig_n = problem_ptr->get_papilo_original_num_variables();
for (size_t sol_idx = 0; sol_idx < context.settings.initial_solutions.size(); ++sol_idx) {
if (timer.check_time_limit()) { break; }
const auto& init_sol = context.settings.initial_solutions[sol_idx];
solution_t<i_t, f_t> sol(*problem_ptr);
rmm::device_uvector<f_t> init_sol_assignment(*init_sol, sol.handle_ptr->get_stream());
Expand Down Expand Up @@ -228,17 +230,18 @@ void diversity_manager_t<i_t, f_t>::add_user_given_solutions(
"reduced objective size must match crushed solution dimension");
// Map each solution to user space with its own problem's scale, so the comparison holds even
// if the original and reduced objective scales ever diverge.
const double input_obj =
[[maybe_unused]] const double input_obj =
(double)presolver_ptr->get_original_objective_scaling_factor() *
std::inner_product(h_ori_obj.begin(),
h_ori_obj.end(),
h_original.begin(),
(double)presolver_ptr->get_original_objective_offset());
const double crushed_obj = (double)reduced_problem.get_objective_scaling_factor() *
std::inner_product(h_red_obj.begin(),
h_red_obj.end(),
h_crushed.begin(),
(double)reduced_problem.get_objective_offset());
[[maybe_unused]] const double crushed_obj =
(double)reduced_problem.get_objective_scaling_factor() *
std::inner_product(h_red_obj.begin(),
h_red_obj.end(),
h_crushed.begin(),
(double)reduced_problem.get_objective_offset());
CUOPT_LOG_DEBUG(
"Crushed initial solution %d through Papilo (%d -> %d vars), objective %g -> %g",
sol_idx,
Expand Down
63 changes: 16 additions & 47 deletions cpp/src/mip_heuristics/early_heuristic.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@

#pragma once

#include <mip_heuristics/problem/problem.cuh>
#include <mip_heuristics/solution/solution.cuh>

#include <cuopt/mathematical_optimization/mip/solver_settings.hpp>

#include <utilities/logger.hpp>

#include <thrust/fill.h>
#include <cuopt/mathematical_optimization/optimization_problem.hpp>

#include <chrono>
#include <functional>
#include <limits>
#include <utility>
#include <vector>

namespace cuopt::mathematical_optimization::mip {
Expand All @@ -34,38 +29,26 @@ template <typename i_t, typename f_t, typename Derived>
class early_heuristic_t {
public:
early_heuristic_t(const optimization_problem_t<i_t, f_t>& op_problem,
const typename mip_solver_settings_t<i_t, f_t>::tolerances_t& tolerances,
early_incumbent_callback_t<f_t> incumbent_callback)
: incumbent_callback_(std::move(incumbent_callback))
: objective_scaling_factor_(op_problem.get_sense() ? -op_problem.get_objective_scaling_factor()
: op_problem.get_objective_scaling_factor()),
objective_offset_(op_problem.get_sense() ? -op_problem.get_objective_offset()
: op_problem.get_objective_offset()),
incumbent_callback_(std::move(incumbent_callback))
{
RAFT_CUDA_TRY(cudaGetDevice(&device_id_));

// Build and preprocess on the original handle, then copy onto our own handle
// so the derived solver can run on a dedicated stream (prevents graph capture conflicts).
problem_t<i_t, f_t> temp_problem(op_problem, tolerances, false);
temp_problem.preprocess_problem();
temp_problem.handle_ptr->sync_stream();
problem_ptr_ = std::make_unique<problem_t<i_t, f_t>>(temp_problem, &handle_);

solution_ptr_ = std::make_unique<solution_t<i_t, f_t>>(*problem_ptr_);
thrust::fill(handle_.get_thrust_policy(),
solution_ptr_->assignment.begin(),
solution_ptr_->assignment.end(),
f_t{0});
solution_ptr_->clamp_within_bounds();
}

bool solution_found() const { return solution_found_; }
f_t get_best_objective() const { return best_objective_; }
// Return the best objective converted to user-space (sense-aware, offset-aware).
f_t get_best_user_objective() const
{
return problem_ptr_->get_user_obj_from_solver_obj(best_objective_);
return objective_scaling_factor_ * (best_objective_ + objective_offset_);
}
// Set the incumbent threshold. `obj` must be in THIS heuristic's solver-space
// (i.e. the space of problem_ptr_). Callers that hold a value from a different
// problem representation (e.g., the original pre-presolve problem) must convert
// it first, otherwise try_update_best will reject valid solutions.
// (i.e. the space of its input problem). Callers that hold a value from a
// different problem representation (e.g., the original pre-presolve problem)
// must convert it first, otherwise try_update_best will reject valid solutions.
void set_best_objective(f_t obj) { best_objective_ = obj; }
const std::vector<f_t>& get_best_assignment() const { return best_assignment_; }

Expand All @@ -81,34 +64,20 @@ class early_heuristic_t {
if (solver_obj >= best_objective_) { return; }
best_objective_ = solver_obj;

RAFT_CUDA_TRY(cudaSetDevice(device_id_));
auto stream = handle_.get_stream();
rmm::device_uvector<f_t> d_assignment(assignment.size(), stream);
raft::copy(d_assignment.data(), assignment.data(), assignment.size(), stream);
problem_ptr_->post_process_assignment(d_assignment, true, stream);
auto user_assignment = cuopt::host_copy(d_assignment, stream);

best_assignment_ = user_assignment;
best_assignment_ = ((Derived*)this)->to_user_assignment(assignment);
solution_found_ = true;
f_t user_obj = problem_ptr_->get_user_obj_from_solver_obj(solver_obj);
f_t user_obj = get_best_user_objective();
// Log and callback are deferred to the shared incumbent_callback_ which enforces
// global monotonicity across all early heuristic instances.
if (incumbent_callback_) {
incumbent_callback_(solver_obj, user_obj, user_assignment, heuristic_name);
incumbent_callback_(solver_obj, user_obj, best_assignment_, heuristic_name);
}
}

int device_id_{0};

// handle_ must be declared before problem_ptr_/solution_ptr_ so it outlives them
// (C++ destroys members in reverse declaration order)
raft::handle_t handle_;

std::unique_ptr<problem_t<i_t, f_t>> problem_ptr_;
std::unique_ptr<solution_t<i_t, f_t>> solution_ptr_;

bool solution_found_{false};
f_t best_objective_{std::numeric_limits<f_t>::infinity()};
f_t objective_scaling_factor_;
f_t objective_offset_;
std::vector<f_t> best_assignment_;

early_incumbent_callback_t<f_t> incumbent_callback_;
Expand Down
Loading
Loading