// Copyright 2010-2021 Google LLC
// 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.

// A minimal TSP solver using MathOpt.
//
// In the Euclidean Traveling Salesperson Problem (TSP), you are given a list of
// n cities, each with an (x, y) coordinate, and you must find an order to visit
// the cities in to minimize the (Euclidean) travel distance.
//
// The MIP "cutset" formulation for the problem is as follows:
//   * Data:
//       n: An integer, the number of cities
//       (x_i, y_i): a pair of floats for each i in 1..n, the location of each
//           city
//       d_ij for all (i, j) pairs of cities, the distance between city i and j.
//   * Decision variables:
//       x_ij: A binary variable, indicates if the edge connecting i and j is
//           used. Note that x_ij == x_ji, because the problem is symmetric. We
//           only create variables for i < j, and have x_ji as an alias for
//           x_ij.
//   * MIP model:
//       minimize sum_{i=1}^n sum_{j=1, j < i}^n d_ij * x_ij
//       s.t. sum_{j=1, j != i}^n x_ij = 2 for all i = 1..n
//            sum_{i in S} sum_{j not in S} x_ij >= 2 for all S subset {1,...,n}
//                                                    |S| >= 3, |S| <= n - 3
//            x_ij in {0, 1}
// The first set of constraints are called the degree constraints, and the
// second set of constraints are called the cutset constraints. There are
// exponentially many cutset, so we cannot add them all at the start of the
// solve. Instead, we will use a solver callback to view each integer solution
// and add any violated cutset constraints that exist.
//
// Note that, while there are exponentially many cutset constraints, we can
// quickly identify violated ones by exploiting that the solution is integer
// and the degree constraints are all already in the model and satisfied. As a
// result, the graph n nodes and edges when x_ij = 1 will be a degree two graph,
// so it will be a collection of cycles. If it is a single large cycle, then the
// solution is feasible, and if there multiple cycles, then taking the nodes of
// any cycle as S produces a violated cutset constraint.
//
// Note that this is a minimal TSP solution, more sophisticated MIP methods are
// possible.

#include <iostream>
#include <optional>

#include "absl/flags/flag.h"
#include "absl/random/random.h"
#include "absl/random/uniform_real_distribution.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/file.h"
#include "ortools/base/init_google.h"
#include "ortools/base/logging.h"
#include "ortools/base/status_builder.h"
#include "ortools/base/status_macros.h"
#include "ortools/math_opt/cpp/math_opt.h"
#include "ortools/port/proto_utils.h"

ABSL_FLAG(int, num_cities, 50, "Number of cities in random TSP.");
ABSL_FLAG(std::string, output, "",
          "Write a svg of the solution here, or to standard out if empty.");
ABSL_FLAG(bool, test_instance, false,
          "Solve the test TSP instead of a random instance.");
ABSL_FLAG(int, threads, 0,
          "How many threads to solve with, or solver default if <= 0.");
ABSL_FLAG(bool, solve_logs, false,
          "Have the solver print logs to standard out.");

namespace {

namespace math_opt = operations_research::math_opt;
using Cycle = std::vector<int>;

// Creates variables modeling the undirected edges for the TSP. For every (i, j)
// pair in [0,n) * [0, n), a variable is created only for j < i, but querying
// for the variable x_ij with j > i returns x_ji. Querying for x_ii (which does
// not exist) gives a CHECK failure.
//
// The Model object passed in to create EdgeVariables must outlive this.
class EdgeVariables {
 public:
  EdgeVariables(math_opt::Model& model, const int n) {
    variables_.resize(n);
    for (int i = 0; i < n; ++i) {
      variables_[i].reserve(i);
      for (int j = 0; j < i; ++j) {
        variables_[i].push_back(
            model.AddBinaryVariable(absl::StrCat("e_", i, "_", j)));
      }
    }
  }

  math_opt::Variable get(const int i, const int j) const {
    CHECK_NE(i, j);
    return i > j ? variables_[i][j] : variables_[j][i];
  }

  int num_cities() const { return variables_.size(); }

 private:
  std::vector<std::vector<math_opt::Variable>> variables_;
};

// Produces a random TSP problem where cities have random locations that are
// I.I.D Uniform [0, 1].
std::vector<std::pair<double, double>> RandomCities(int num_cities) {
  absl::BitGen rand;
  std::vector<std::pair<double, double>> cities;
  for (int i = 0; i < num_cities; ++i) {
    cities.push_back({absl::Uniform<double>(rand, 0.0, 1.0),
                      absl::Uniform<double>(rand, 0.0, 1.0)});
  }
  return cities;
}

std::vector<std::pair<double, double>> TestCities() {
  return {{0, 0}, {0, 0.1}, {0.1, 0}, {0.1, 0.1},
          {1, 0}, {1, 0.1}, {0.9, 0}, {0.9, 0.1}};
}

// Given an n city TSP instance, computes the n by n distance matrix using the
// Euclidean distance.
std::vector<std::vector<double>> DistanceMatrix(
    const std::vector<std::pair<double, double>>& cities) {
  const int num_cities = cities.size();
  std::vector<std::vector<double>> distance_matrix(
      num_cities, std::vector<double>(num_cities, 0.0));
  for (int i = 0; i < num_cities; ++i) {
    for (int j = 0; j < num_cities; ++j) {
      if (i != j) {
        const double dx = cities[i].first - cities[j].first;
        const double dy = cities[i].second - cities[j].second;
        distance_matrix[i][j] = std::sqrt(dx * dx + dy * dy);
      }
    }
  }
  return distance_matrix;
}

// Given the EdgeVariables and a var_values containing the value of each edge in
// a solution, returns an n by n boolean matrix of which edges are used (with
// false diagonal elements). It is assumed that var_values are approximately 0-1
// integer.
std::vector<std::vector<bool>> EdgeValues(
    const EdgeVariables& edge_vars,
    const math_opt::VariableMap<double>& var_values) {
  const int n = edge_vars.num_cities();
  std::vector<std::vector<bool>> edge_values(n, std::vector<bool>(n, false));
  for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n; ++j) {
      if (i != j) {
        edge_values[i][j] = var_values.at(edge_vars.get(i, j)) > 0.5;
      }
    }
  }
  return edge_values;
}

// Given an n by n boolean matrix of edge values, returns a cycle decomposition.
// it is assumed that edge values respects the degree constraints (each row has
// only two true entries). Each cycle is represented as a list of cities with
// no repeats.
std::vector<Cycle> FindCycles(
    const std::vector<std::vector<bool>>& edge_values) {
  // Algorithm: maintain a "visited" bit for each city indicating if we have
  // formed a cycle containing this city. Consider the cities in order. When you
  // find an unvisited city, start a new cycle beginning at this city. Then,
  // build the cycle by finding an unvisited neighbor until no such neighbor
  // exists (every city will have two neighbors, but eventually both will be
  // visited). To find the "unvisited neighbor", we simply do a linear scan
  // over the cities, checking both the adjacency matrix and the visited bit.
  //
  // Note that for this algorithm, in each cycle, the city with lowest index
  // will be first, and the cycles will be sorted by their city of lowest index.
  // This is an implementation detail and should not be relied upon.
  const int n = edge_values.size();
  std::vector<Cycle> result;
  std::vector<bool> visited(n, false);
  for (int i = 0; i < n; ++i) {
    if (visited[i]) {
      continue;
    }
    std::vector<int> cycle;
    std::optional<int> next = i;
    while (next.has_value()) {
      cycle.push_back(*next);
      visited[*next] = true;
      int current = *next;
      next = std::nullopt;
      // Scan for an unvisited neighbor. We can start at i+1 since we know that
      // everything from i back is visited.
      for (int j = i + 1; j < n; ++j) {
        if (!visited[j] && edge_values[current][j]) {
          next = j;
          break;
        }
      }
    }
    result.push_back(cycle);
  }
  return result;
}

// Given a cycle and an EdgeVariables, returns the cutset constraint for the set
// of nodes in cycle.
math_opt::BoundedLinearExpression CutsetConstraint(
    const Cycle& cycle, const EdgeVariables& edge_vars) {
  const int n = edge_vars.num_cities();
  const absl::flat_hash_set<int> cycle_as_set(cycle.begin(), cycle.end());
  std::vector<int> not_in_cycle;
  for (int i = 0; i < n; ++i) {
    if (!cycle_as_set.contains(i)) {
      not_in_cycle.push_back(i);
    }
  }
  math_opt::LinearExpression cutset_edges;
  for (const int in_cycle : cycle) {
    for (const int out_of_cycle : not_in_cycle) {
      cutset_edges += edge_vars.get(in_cycle, out_of_cycle);
    }
  }
  return cutset_edges >= 2;
}

// Solves the TSP by returning the ordering of the cities that minimizes travel
// distance.
absl::StatusOr<Cycle> SolveTsp(
    const std::vector<std::pair<double, double>>& cities) {
  const int n = cities.size();
  const std::vector<std::vector<double>> distance_matrix =
      DistanceMatrix(cities);
  CHECK_GE(n, 3);
  math_opt::Model model("tsp");
  const EdgeVariables edge_vars(model, n);
  math_opt::LinearExpression edge_cost;
  for (int i = 0; i < n; ++i) {
    for (int j = i + 1; j < n; ++j) {
      edge_cost += edge_vars.get(i, j) * distance_matrix[i][j];
    }
  }
  model.Minimize(edge_cost);

  // Add the degree constraints
  for (int i = 0; i < n; ++i) {
    math_opt::LinearExpression neighbors;
    for (int j = 0; j < n; ++j) {
      if (i != j) {
        neighbors += edge_vars.get(i, j);
      }
    }
    model.AddLinearConstraint(neighbors == 2, absl::StrCat("n_", i));
  }
  math_opt::SolveArguments args;
  args.parameters.enable_output = absl::GetFlag(FLAGS_solve_logs);
  const int threads = absl::GetFlag(FLAGS_threads);
  if (threads > 0) {
    args.parameters.threads = threads;
  }
  args.callback_registration.events.insert(
      math_opt::CallbackEvent::kMipSolution);
  args.callback_registration.add_lazy_constraints = true;
  args.callback = [&edge_vars](const math_opt::CallbackData& cb_data) {
    // At event CallbackEvent::kMipSolution, a solution is always present.
    CHECK(cb_data.solution.has_value());
    const std::vector<Cycle> cycles =
        FindCycles(EdgeValues(edge_vars, *cb_data.solution));
    math_opt::CallbackResult result;
    if (cycles.size() > 1) {
      for (const Cycle& cycle : cycles) {
        result.AddLazyConstraint(CutsetConstraint(cycle, edge_vars));
      }
    }
    return result;
  };
  ASSIGN_OR_RETURN(const math_opt::SolveResult result,
                   math_opt::Solve(model, math_opt::SolverType::kGurobi, args));
  if (result.termination.reason != math_opt::TerminationReason::kOptimal) {
    return util::InternalErrorBuilder()
           << "Expected TSP solve terminate with reason optimal, found: "
           << result.termination;
  }
  std::cout << "Route length: " << result.objective_value() << std::endl;
  const std::vector<Cycle> cycles =
      FindCycles(EdgeValues(edge_vars, result.variable_values()));
  CHECK_EQ(cycles.size(), 1);
  CHECK_EQ(cycles[0].size(), n);
  return cycles[0];
}

// Produces an SVG to draw a route for a TSP.
std::string RouteSvg(const std::vector<std::pair<double, double>>& cities,
                     const Cycle& cycle) {
  constexpr int image_px = 1000;
  constexpr int r = 5;
  constexpr int image_plus_border = image_px + 2 * r;
  std::vector<std::string> svg_lines;
  svg_lines.push_back(absl::StrCat("<svg width=\"", image_plus_border,
                                   "\" height=\"", image_plus_border, "\">"));
  std::vector<std::string> polygon_coords;
  for (const int city : cycle) {
    const int x =
        static_cast<int>(std::round(cities[city].first * image_px)) + r;
    const int y =
        static_cast<int>(std::round(cities[city].second * image_px)) + r;
    svg_lines.push_back(absl::StrCat("<circle cx=\"", x, "\" cy=\"", y,
                                     "\" r=\"", r, "\" fill=\"blue\" />"));
    polygon_coords.push_back(absl::StrCat(x, ",", y));
  }
  std::string polygon_coords_string = absl::StrJoin(polygon_coords, " ");
  svg_lines.push_back(
      absl::StrCat("<polygon fill=\"none\" stroke=\"blue\" points=\"",
                   polygon_coords_string, "\" />"));
  svg_lines.push_back("</svg>");
  return absl::StrJoin(svg_lines, "\n");
}

void RealMain() {
  std::vector<std::pair<double, double>> cities;
  if (absl::GetFlag(FLAGS_test_instance)) {
    cities = TestCities();
  } else {
    cities = RandomCities(absl::GetFlag(FLAGS_num_cities));
  }
  absl::StatusOr<Cycle> solution = SolveTsp(cities);
  if (!solution.ok()) {
    LOG(QFATAL) << solution.status();
  }
  const std::string svg = RouteSvg(cities, *solution);
  if (absl::GetFlag(FLAGS_output).empty()) {
    std::cout << svg << std::endl;
  } else {
    QCHECK_OK(
        file::SetContents(absl::GetFlag(FLAGS_output), svg, file::Defaults()));
  }
}

}  // namespace

int main(int argc, char** argv) {
  InitGoogle(argv[0], &argc, &argv, true);
  RealMain();
  return 0;
}
