Closed lucidfrontier45 closed 1 day ago
I cannot reproduce from JuMP.
julia> using JuMP, HiGHS
julia> function solve_tsp(dc)
n = size(dc, 1)
model = Model(HiGHS.Optimizer)
@variable(model, x[1:n, 1:n], Bin)
@variable(model, 1 <= u[1:n] <= n - 1)
@objective(model, Min, sum(dc .* x))
for i in 1:n
@constraint(model, x[i, i] == 0)
@constraint(model, sum(x[i, :]) == 1)
@constraint(model, sum(x[:, i]) == 1)
end
fix(u[1], 0; force = true)
for i in 2:n, j in 2:n
if i != j
@constraint(model, u[i] - u[j] <= n * (1 - x[i, j]) - 1)
end
end
optimize!(model)
x_vals = round.(Int, value.(x))
for i in 1:n
@assert sum(x_vals[i, :]) == 1
@assert sum(x_vals[:, i]) == 1
end
tour = [1]
current = 1
while true
j = findfirst(==(1), x_vals[current, :])
if j === 1
break
end
push!(tour, j)
current = j
end
total_distance = round(Int, objective_value(model))
return total_distance, tour
end
solve_tsp (generic function with 1 method)
julia> coords = [
(38.24, 20.42),
(39.57, 26.15),
(40.56, 25.32),
(36.26, 23.12),
(33.48, 10.54),
(37.56, 12.19),
(38.42, 13.11),
(37.52, 20.44),
(41.23, 9.10),
(41.17, 13.05),
(36.08, -5.21),
(38.47, 15.13),
(38.15, 15.35),
(37.51, 15.17),
(35.49, 14.32),
(39.36, 19.56),
]
16-element Vector{Tuple{Float64, Float64}}:
(38.24, 20.42)
(39.57, 26.15)
(40.56, 25.32)
(36.26, 23.12)
(33.48, 10.54)
(37.56, 12.19)
(38.42, 13.11)
(37.52, 20.44)
(41.23, 9.1)
(41.17, 13.05)
(36.08, -5.21)
(38.47, 15.13)
(38.15, 15.35)
(37.51, 15.17)
(35.49, 14.32)
(39.36, 19.56)
julia> function from_coordinates(coords)
mat = zeros(length(coords), length(coords))
for (i, (x1, y1)) in enumerate(coords)
for (j, (x2, y2)) in enumerate(coords)
mat[i, j] = round(Int, abs(x1 - x2) + abs(y1 - y2))
end
end
return mat
end
from_coordinates (generic function with 1 method)
julia> dc = from_coordinates(coords)
16×16 Matrix{Float64}:
0.0 7.0 7.0 5.0 15.0 9.0 7.0 1.0 14.0 10.0 28.0 6.0 5.0 6.0 9.0 2.0
7.0 0.0 2.0 6.0 22.0 16.0 14.0 8.0 19.0 15.0 35.0 12.0 12.0 13.0 16.0 7.0
7.0 2.0 0.0 7.0 22.0 16.0 14.0 8.0 17.0 13.0 35.0 12.0 12.0 13.0 16.0 7.0
5.0 6.0 7.0 0.0 15.0 12.0 12.0 4.0 19.0 15.0 29.0 10.0 10.0 9.0 10.0 7.0
15.0 22.0 22.0 15.0 0.0 6.0 8.0 14.0 9.0 10.0 18.0 10.0 9.0 9.0 6.0 15.0
9.0 16.0 16.0 12.0 6.0 0.0 2.0 8.0 7.0 4.0 19.0 4.0 4.0 3.0 4.0 9.0
7.0 14.0 14.0 12.0 8.0 2.0 0.0 8.0 7.0 3.0 21.0 2.0 3.0 3.0 4.0 7.0
1.0 8.0 8.0 4.0 14.0 8.0 8.0 0.0 15.0 11.0 27.0 6.0 6.0 5.0 8.0 3.0
14.0 19.0 17.0 19.0 9.0 7.0 7.0 15.0 0.0 4.0 19.0 9.0 9.0 10.0 11.0 12.0
10.0 15.0 13.0 15.0 10.0 4.0 3.0 11.0 4.0 0.0 23.0 5.0 5.0 6.0 7.0 8.0
28.0 35.0 35.0 29.0 18.0 19.0 21.0 27.0 19.0 23.0 0.0 23.0 23.0 22.0 20.0 28.0
6.0 12.0 12.0 10.0 10.0 4.0 2.0 6.0 9.0 5.0 23.0 0.0 1.0 1.0 4.0 5.0
5.0 12.0 12.0 10.0 9.0 4.0 3.0 6.0 9.0 5.0 23.0 1.0 0.0 1.0 4.0 5.0
6.0 13.0 13.0 9.0 9.0 3.0 3.0 5.0 10.0 6.0 22.0 1.0 1.0 0.0 3.0 6.0
9.0 16.0 16.0 10.0 6.0 4.0 4.0 8.0 11.0 7.0 20.0 4.0 4.0 3.0 0.0 9.0
2.0 7.0 7.0 7.0 15.0 9.0 7.0 3.0 12.0 8.0 28.0 5.0 5.0 6.0 9.0 0.0
julia> total_distance, tour = solve_tsp(dc)
Running HiGHS 1.7.2 (git hash: 5ce7a2753): Copyright (c) 2024 HiGHS under MIT licence terms
Coefficient ranges:
Matrix [1e+00, 2e+01]
Cost [1e+00, 4e+01]
Bound [1e+00, 2e+01]
RHS [1e+00, 2e+01]
Presolving model
242 rows, 255 cols, 1110 nonzeros 0s
242 rows, 255 cols, 1110 nonzeros 0s
Objective function is integral with scale 1
Solving MIP model with:
242 rows
255 cols (240 binary, 0 integer, 0 implied int., 15 continuous)
1110 nonzeros
Nodes | B&B Tree | Objective Bounds | Dynamic Constraints | Work
Proc. InQueue | Leaves Expl. | BestBound BestSol Gap | Cuts InLp Confl. | LpIters Time
0 0 0 0.00% 0 inf inf 0 0 0 0 0.0s
0 0 0 0.00% 73.06666667 inf inf 0 0 0 44 0.0s
R 0 0 0 0.00% 80 106 24.53% 1100 61 20 223 0.1s
L 0 0 0 0.00% 81 91 10.99% 1585 47 20 306 0.4s
L 0 0 0 0.00% 81 90 10.00% 1642 52 20 925 0.7s
20.0% inactive integer columns, restarting
Model after restart has 200 rows, 207 cols (192 bin., 0 int., 0 impl., 15 cont.), and 888 nonzeros
0 0 0 0.00% 81 90 10.00% 19 0 0 1828 0.7s
0 0 0 0.00% 81 90 10.00% 19 17 4 1851 0.7s
4.7% inactive integer columns, restarting
Model after restart has 191 rows, 198 cols (183 bin., 0 int., 0 impl., 15 cont.), and 843 nonzeros
0 0 0 0.00% 82 90 8.89% 20 0 0 2841 1.1s
0 0 0 0.00% 82 90 8.89% 20 15 2 2873 1.1s
L 0 0 0 0.00% 82 89 7.87% 832 21 2 4385 1.9s
Solving report
Status Optimal
Primal bound 89
Dual bound 89
Gap 0% (tolerance: 0.01%)
Solution status feasible
89 (objective)
0 (bound viol.)
1.13242748512e-14 (int. viol.)
0 (row viol.)
Timing 5.33 (total)
0.01 (presolve)
0.00 (postsolve)
Nodes 2085
LP iterations 27727 (total)
7264 (strong br.)
2021 (separation)
4351 (heuristics)
Nothing seems wrong with the solution:
julia> solution_summary(model)
* Solver : HiGHS
* Status
Result count : 1
Termination status : OPTIMAL
Message from the solver:
"kHighsModelStatusOptimal"
* Candidate solution (result #1)
Primal status : FEASIBLE_POINT
Dual status : NO_SOLUTION
Objective value : 8.90000e+01
Objective bound : 8.90000e+01
Relative gap : 0.00000e+00
* Work counters
Solve time (sec) : 5.33034e+00
Simplex iterations : 27727
Barrier iterations : -1
Node count : 2085
and the solution satisfies all constraints to 1e-13 or so:
julia> primal_feasibility_report(model)
Dict{Any, Float64} with 29 entries:
x[1,7] + x[2,7] + x[3,7] + x[4,7] + x[5,7] + x[6,7] + x[7,7] + x[8,7] + x[9,7] + x[10,7] + x[11,7] + x[12,7] + x[13,7] + x[14,7] + x[15,7] + x[16,7] = 1 => 1.44329e-15
u[8] ≤ 15 => 8.33111e-13
x[16,3] binary => 1.13243e-14
16 x[14,15] + u[14] - u[15] ≤ 15 => 1.06581e-14
x[1,13] binary => 1.11022e-14
x[4,2] binary => 1.13243e-14
x[4,8] binary => 1.13243e-14
x[1,16] + x[2,16] + x[3,16] + x[4,16] + x[5,16] + x[6,16] + x[7,16] + x[8,16] + x[9,16] + x[10,16] + x[11,16] + x[12,16] + x[13,16] + x[14,16] + x[15,16] + x[16,16] = 1 => 5.55112e-15
x[6,1] + x[6,2] + x[6,3] + x[6,4] + x[6,5] + x[6,6] + x[6,7] + x[6,8] + x[6,9] + x[6,10] + x[6,11] + x[6,12] + x[6,13] + x[6,14] + x[6,15] + x[6,16] = 1 => 1.44329e-15
x[3,2] binary => 1.13243e-14
x[1,1] + x[2,1] + x[3,1] + x[4,1] + x[5,1] + x[6,1] + x[7,1] + x[8,1] + x[9,1] + x[10,1] + x[11,1] + x[12,1] + x[13,1] + x[14,1] + x[15,1] + x[16,1] = 1 => 3.77476e-15
x[12,1] + x[12,2] + x[12,3] + x[12,4] + x[12,5] + x[12,6] + x[12,7] + x[12,8] + x[12,9] + x[12,10] + x[12,11] + x[12,12] + x[12,13] + x[12,14] + x[12,15] + x[12,16] = 1 => 5.55112e-15
16 x[15,5] - u[5] + u[15] ≤ 15 => 9.23706e-14
x[2,4] binary => 1.13243e-14
x[1,4] + x[2,4] + x[3,4] + x[4,4] + x[5,4] + x[6,4] + x[7,4] + x[8,4] + x[9,4] + x[10,4] + x[11,4] + x[12,4] + x[13,4] + x[14,4] + x[15,4] + x[16,4] = 1 => 1.13243e-14
x[1,8] binary => 1.13243e-14
x[6,7] binary => 1.44329e-15
x[2,3] binary => 1.13243e-14
16 x[16,3] - u[3] + u[16] ≤ 15 => 7.10543e-15
16 x[4,8] + u[4] - u[8] ≤ 15 => 8.88178e-15
x[1,13] + x[2,13] + x[3,13] + x[4,13] + x[5,13] + x[6,13] + x[7,13] + x[8,13] + x[9,13] + x[10,13] + x[11,13] + x[12,13] + x[13,13] + x[14,13] + x[15,13] + x[16,13] = 1 => 1.11022e-14
16 x[3,2] - u[2] + u[3] ≤ 15 => 6.03961e-14
x[16,1] binary => 1.13243e-14
x[8,1] binary => 7.54952e-15
x[1,1] + x[1,2] + x[1,3] + x[1,4] + x[1,5] + x[1,6] + x[1,7] + x[1,8] + x[1,9] + x[1,10] + x[1,11] + x[1,12] + x[1,13] + x[1,14] + x[1,15] + x[1,16] = 1 => 2.22045e-16
16 x[7,12] + u[7] - u[12] ≤ 15 => 1.7053e-13
x[3,1] + x[3,2] + x[3,3] + x[3,4] + x[3,5] + x[3,6] + x[3,7] + x[3,8] + x[3,9] + x[3,10] + x[3,11] + x[3,12] + x[3,13] + x[3,14] + x[3,15] + x[3,16] = 1 => 1.13243e-14
x[8,1] + x[8,2] + x[8,3] + x[8,4] + x[8,5] + x[8,6] + x[8,7] + x[8,8] + x[8,9] + x[8,10] + x[8,11] + x[8,12] + x[8,13] + x[8,14] + x[8,15] + x[8,16] = 1 => 7.54952e-15
x[12,16] binary => 5.55112e-15
(89, [1, 13, 14, 15, 5, 11, 9, 10, 6, 7, 12, 16, 3, 2, 4, 8])
Ah. @lucidfrontier45, you have a bug in your code:
[int(result.variable_values(x[i][j])) for j in range(n)] for i in range(n)
Note that int
in Python rounds down, not to the nearest.
>>> int(0.9999)
0
See the docstring:
>>> help(int)
Help on class int in module builtins:
class int(object)
| int([x]) -> integer
| int(x, base=10) -> integer
|
| Convert a number or string to an integer, or return 0 if no arguments
| are given. If x is a number, return x.__int__(). For floating point
| numbers, this truncates towards zero.
|
| If x is not a number or if base is given, then x must be a string,
| bytes, or bytearray instance representing an integer literal in the
| given base. The literal can be preceded by '+' or '-' and be surrounded
| by whitespace. The base defaults to 10. Valid bases are 0 and 2-36.
| Base 0 means to interpret the base from the string as an integer literal.
| >>> int('0b100', base=0)
The important part is
For floating point numbers, this truncates towards zero.
This is not a bug in HiGHS.
@odow Thank you so much! Now both programs work find.
Hi.
I implemented TSP with MTZ subtour elimination approach. I found that Highs reported feasible solutions but they violate the constraint and were actually infeasible. I implemented the model by two frameworks.
Both of them show the same behaviour so I think it is not the problem of those frameworks but of Highs itself. In Python code if I use CP_SAT solver the result is correct so the modeling should also be valid.
Here are my one-file source codes. They are implemented exactly in the same way.