use crate::{ComparisonOp, LinearExpr, OptimizationDirection, Variable};
use std::io;
#[allow(clippy::all)]
pub fn solve_tsp(problem: &TspProblem) -> Tour {
info!("starting, problem name: {}", problem.name);
let num_points = problem.points.len();
let mut lp_problem = crate::Problem::new(OptimizationDirection::Minimize);
let mut edge_vars = vec![vec![]; num_points];
for i in 0..num_points {
for j in 0..num_points {
let var = if j < i {
edge_vars[j][i]
} else {
lp_problem.add_binary_var(problem.dist(i, j))
};
edge_vars[i].push(var);
}
}
for i in 0..num_points {
let mut edges_sum = LinearExpr::empty();
for j in 0..num_points {
if i != j {
edges_sum.add(edge_vars[i][j], 1.0);
}
}
lp_problem.add_constraint(edges_sum, ComparisonOp::Eq, 2.0);
}
loop {
let solution = lp_problem.solve().unwrap();
info!(
"solved integer problem, obj. value: {:.2}",
solution.objective(),
);
let subtours = find_subtours(&solution, &edge_vars);
if subtours.len() == 1 {
info!("found optimal solution, cost: {:.2}", solution.objective());
return tour_from_lp_solution(&solution, &edge_vars);
}
info!(
"solution has {} subtours, adding elimination constraints",
subtours.len()
);
for subtour in &subtours {
let mut in_subtour = vec![false; num_points];
for &node in subtour {
in_subtour[node] = true;
}
let mut cut_edges_sum = LinearExpr::empty();
for i in 0..num_points {
for j in 0..i {
if in_subtour[i] != in_subtour[j] {
cut_edges_sum.add(edge_vars[i][j], 1.0);
}
}
}
lp_problem.add_constraint(cut_edges_sum, ComparisonOp::Ge, 2.0);
}
}
}
#[derive(Clone, Copy, Debug)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn sqr_dist(self, other: Self) -> f64 {
(self.x - other.x) * (self.x - other.x) + (self.y - other.y) * (self.y - other.y)
}
fn dist(self, other: Self) -> f64 {
f64::sqrt(self.sqr_dist(other))
}
}
#[derive(Debug)]
pub struct TspProblem {
pub name: String,
points: Vec<Point>,
}
fn read_line<R: io::BufRead>(mut input: R) -> io::Result<Vec<String>> {
let mut line = String::new();
input.read_line(&mut line)?;
Ok(line.split_whitespace().map(|tok| tok.to_owned()).collect())
}
fn parse_num<T: std::str::FromStr>(input: &str, line_num: usize) -> io::Result<T> {
input.parse::<T>().or(Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("line {}: couldn't parse number", line_num),
)))
}
impl TspProblem {
pub fn dist(&self, n1: usize, n2: usize) -> f64 {
self.points[n1].dist(self.points[n2])
}
pub fn parse<R: io::BufRead>(mut input: R) -> io::Result<TspProblem> {
let mut name = String::new();
let mut dimension = None;
let mut line_num = 0;
loop {
let line = read_line(&mut input)?;
if line.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"premature end of header".to_string(),
));
}
let mut keyword = line[0].clone();
if keyword.ends_with(":") {
keyword.pop();
}
if keyword == "NAME" {
name = line.last().unwrap().clone();
} else if keyword == "TYPE" {
if line.last().unwrap() != "TSP" {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"only problems with TYPE: TSP supported".to_string(),
));
}
} else if keyword == "EDGE_WEIGHT_TYPE" {
if line.last().unwrap() != "EUC_2D" {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"only problems with EDGE_WEIGHT_TYPE: EUC_2D supported".to_string(),
));
}
} else if keyword == "DIMENSION" {
let dim: usize = parse_num(line.last().as_ref().unwrap(), line_num)?;
dimension = Some(dim);
} else if keyword == "NODE_COORD_SECTION" {
break;
}
line_num += 1;
}
let num_points = dimension.ok_or(io::Error::new(
io::ErrorKind::InvalidData,
"no DIMENSION specified".to_string(),
))?;
if num_points > 100_000 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("problem dimension: {} is suspiciously large", num_points),
));
}
let mut point_opts = vec![None; num_points];
for _ in 0..num_points {
let line = read_line(&mut input)?;
let node_num: usize = parse_num(&line[0], line_num)?;
let x: f64 = parse_num(&line[1], line_num)?;
let y: f64 = parse_num(&line[2], line_num)?;
if node_num == 0 || node_num > num_points {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("line {}: bad node number: {}", line_num, node_num),
));
}
if point_opts[node_num - 1].is_some() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("line {}: node {} specified twice", line_num, node_num),
));
}
point_opts[node_num - 1] = Some(Point { x, y });
line_num += 1;
}
let line = read_line(input)?;
if line.len() > 1 || (line.len() == 1 && line[0] != "EOF") {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("line {}: expected EOF", line_num),
));
}
let mut points = vec![];
for (i, po) in point_opts.into_iter().enumerate() {
if let Some(p) = po {
points.push(p);
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("node {} is not specified", i),
));
}
}
Ok(TspProblem { name, points })
}
}
#[derive(Debug)]
pub struct Tour(Vec<usize>);
impl Tour {
#[allow(clippy::all)]
pub fn to_string(&self) -> String {
self.0
.iter()
.map(|n| (n + 1).to_string())
.collect::<Vec<_>>()
.join(" ")
}
pub fn to_svg(&self, problem: &TspProblem) -> String {
let cmp_f64 = |x: &f64, y: &f64| x.partial_cmp(y).unwrap();
let min_x = problem.points.iter().map(|p| p.x).min_by(cmp_f64).unwrap();
let max_x = problem.points.iter().map(|p| p.x).max_by(cmp_f64).unwrap();
let min_y = problem.points.iter().map(|p| p.y).min_by(cmp_f64).unwrap();
let max_y = problem.points.iter().map(|p| p.y).max_by(cmp_f64).unwrap();
let width = 600;
let margin = 50;
let scale = ((width - 2 * margin) as f64) / (max_x - min_x);
let height = f64::round((max_y - min_y) * scale) as usize + 2 * margin;
let mut svg = String::new();
svg += "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
svg += "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n";
svg += " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n";
svg += &format!(
"<svg width=\"{}px\" height=\"{}px\" version=\"1.1\"",
width, height
);
svg += " xmlns=\"http://www.w3.org/2000/svg\">\n";
use std::fmt::Write;
svg += " <path fill=\"none\" stroke=\"black\" stroke-width=\"4px\" d=\"\n";
for &i in &self.0 {
let p = problem.points[i];
let px = f64::round((p.x - min_x) * scale) as usize + margin;
let py = f64::round((p.y - min_y) * scale) as usize + margin;
if i == 0 {
writeln!(&mut svg, " M {} {}", px, py).unwrap();
} else {
writeln!(&mut svg, " L {} {}", px, py).unwrap();
}
}
svg += " Z\n";
svg += " \"/>\n";
svg += "</svg>\n";
svg
}
}
fn find_subtours(solution: &crate::Solution, edge_vars: &[Vec<Variable>]) -> Vec<Vec<usize>> {
let num_points = edge_vars.len();
let mut visited = vec![false; num_points];
let mut subtours = vec![];
for start in 0..num_points {
if visited[start] {
continue;
}
let mut tour = vec![];
let mut cur = start;
loop {
visited[cur] = true;
tour.push(cur);
let mut found_next = false;
for neighbor in 0..num_points {
if !visited[neighbor] && solution[edge_vars[cur][neighbor]].round() == 1.0 {
cur = neighbor;
found_next = true;
break;
}
}
if !found_next {
break;
}
}
subtours.push(tour);
}
subtours
}
fn tour_from_lp_solution(lp_solution: &crate::Solution, edge_vars: &[Vec<Variable>]) -> Tour {
let num_points = edge_vars.len();
let mut tour = vec![];
let mut is_visited = vec![false; num_points];
let mut cur_point = 0;
for _ in 0..num_points {
assert!(!is_visited[cur_point]);
is_visited[cur_point] = true;
tour.push(cur_point);
for neighbor in 0..num_points {
if !is_visited[neighbor] && lp_solution[edge_vars[cur_point][neighbor]].round() == 1.0 {
cur_point = neighbor;
break;
}
}
}
assert_eq!(tour.len(), num_points);
Tour(tour)
}