#![allow(non_snake_case)]
use clap::Parser;
use mixingcut::io_operations;
use mixingcut::io_operations::write_solution_matrix;
use mixingcut::maxcut_oracle::get_Q_norm;
use mixingcut::sdp_solver::{solve_maxcut_sdp, SolveOptions, WarmStart};
use mixingcut::step_rules::generate_step_rule;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
#[clap(short, long)]
input_path: String,
#[clap(short, long, default_value = "output.txt")]
output_path: String,
#[clap(short, long, default_value = "0")]
rank: usize,
#[clap(short, long, default_value = "1e-2")]
tolerance: f64,
#[clap(short, long, default_value = "1000")]
max_iters: usize,
#[clap(short, long, default_value = "coord_no_step")]
step_rule: String,
#[clap(long, default_value = "1")]
index_correction: usize,
#[clap(short, long, default_value = "0")]
dual_bound: usize,
#[clap(short, long, default_value = "1")]
verbose: usize,
#[clap(long, default_value = "100")]
rounding_iters: usize,
#[clap(long, default_value = "128")]
beam_width: usize,
}
fn main() {
let args: Args = Args::parse();
let index_correction = args.index_correction;
let Q = io_operations::read_graph_matrix(&args.input_path, index_correction);
let Q_norm = get_Q_norm(&Q);
let alpha_safe = 1.0 / Q_norm;
let n = Q.shape().0;
let step_rule = generate_step_rule(&args.step_rule, alpha_safe);
let verbose = args.verbose;
if verbose == 1 {
println!("------------------------------------------------------------------");
println!(" MixingCut v0.1.4 - MAX CUT SDP Solver ");
println!(" (c) Dustin Kenefake, Texas A&M University, 2024 ");
println!("------------------------------------------------------------------");
}
let k = match args.rank {
0 => 2 * (n as f64).log2() as usize,
1 => (2.0 * n as f64).sqrt() as usize,
_ => args.rank,
};
if verbose == 1 {
println!("Problem Statistics:");
println!("Size of Q {} {}", n, n);
println!("NNZ(Q) {}", Q.nnz());
println!("Q norm {}", Q_norm);
println!("Rank {}", k);
println!("------------------------------------------------------------------");
println!(
"{0: <20} | {1: <20} | {2: <20}",
"Iteration", "Primal Value", "Time(sec)"
);
}
let result = solve_maxcut_sdp(
&Q,
&SolveOptions {
rank: Some(k),
seed: None,
max_iterations: args.max_iters,
min_stationarity_iterations: 20,
objective_tolerance: args.tolerance,
stationarity_tolerance: 1e-4,
rounding_iterations: args.rounding_iters,
beam_width: Some(args.beam_width),
compute_dual_bound: args.dual_bound == 1,
compute_rounding: true,
step_rule,
verbose: verbose == 1,
warm_start: WarmStart::Random,
},
);
if verbose == 1 {
println!("------------------------------------------------------------------")
}
if verbose == 1 {
if let (Some(rounded_objective), Some(rounded_solution)) =
(result.rounded_objective, result.rounded_solution.as_ref())
{
println!(
"Rounded solution: {:?} {:?}",
rounded_objective, rounded_solution
);
}
if let (Some(best_obj), Some(best_sol)) = (
result.locally_improved_objective,
result.locally_improved_solution.as_ref(),
) {
println!(
"Rounded solution with local search: {:?} {:?}",
best_obj, best_sol
);
}
}
if let Some(dual_bound) = result.dual_bound {
if verbose == 1 {
println!("Dual bound: {:?}", dual_bound);
}
}
let rounded_solution = result
.rounded_solution
.expect("CLI solve should always compute a rounded solution");
let rounded_objective = result
.rounded_objective
.expect("CLI solve should always compute a rounded objective");
write_solution_matrix(
&args.output_path,
rounded_solution,
rounded_objective,
result.relaxed_objective,
);
}