1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
import re
import matplotlib.pyplot as plt
import numpy as np
import math as math
def parse_highs_log(log_file_path):
last_full_entry = []
current_entry = []
found_solution = False
with open(log_file_path, "r") as f:
for line in f:
if "Running HiGHS" in line:
if found_solution:
last_full_entry = current_entry
current_entry = [line]
found_solution = False
else:
current_entry.append(line)
if "Writing the solution to" in line:
found_solution = True
if not last_full_entry:
last_full_entry = current_entry
if not last_full_entry:
return None, None, None, None, None, None
time_values, best_bound_values, best_sol_values, in_queue_values, expl_values, gap_values = (
[],
[],
[],
[],
[],
[],
)
for line in last_full_entry:
match = re.search(r"\dk?\s+\d+\.\ds$", line)
if not match:
continue
tokens = line.split()
if len(tokens) == 13:
tokens = tokens[1:]
assert len(tokens) == 12, f"{line}"
in_queue_values.append(float(tokens[1])) # InQueue
expl_values.append(float(tokens[3].replace("%", ""))) # Expl.%
best_bound_values.append(float(tokens[4].replace("inf", "nan"))) # Best Bound
best_sol_values.append(float(tokens[5].replace("inf", "nan"))) # Best Sol
gap_values.append(
float(tokens[6].replace("%", "").replace("inf", "nan").replace("Large", "nan"))
) # Gap%
time_values.append(float(tokens[11].replace("s", ""))) # Time
return time_values, best_bound_values, best_sol_values, in_queue_values, expl_values, gap_values
def plot_highs_log(
time_values, best_bound_values, best_sol_values, in_queue_values, expl_values, gap_values
):
fig, ax1 = plt.subplots(figsize=(10, 6))
best_bound_colour = "blue"
best_solution_colour = "green"
in_queue_colour = "red"
explored_colour = "purple"
gap_colour = "orange"
# Plot Objective Bounds
ax1.plot(time_values, best_bound_values, label="Best Bound", color=best_bound_colour)
ax1.plot(time_values, best_sol_values, label="Best Solution", color=best_solution_colour)
ax1.set_xlabel("Time (seconds)")
ax1.set_ylabel("Objective Bounds", color=best_bound_colour, labelpad=15)
ax1.tick_params(axis="y", labelcolor=best_bound_colour)
# Limit y-axis to the range between min and max of the non-NaN values
valid_gap_index = next(i for i, gap in enumerate(gap_values) if not np.isnan(gap))
min_y = min(best_bound_values[valid_gap_index], best_sol_values[valid_gap_index])
max_y = max(best_bound_values[valid_gap_index], best_sol_values[valid_gap_index])
padding = (max_y - min_y) * 0.1
ax1.set_ylim(min_y - padding, max_y + padding)
# Add second y-axis for InQueue values
ax2 = ax1.twinx()
ax2.plot(time_values, in_queue_values, label="InQueue", color=in_queue_colour)
# ax2.set_ylabel("InQueue", color=in_queue_colour, loc="top", labelpad=12)
ax2.yaxis.label.set_rotation(0)
ax2.tick_params(axis="y", labelcolor=in_queue_colour)
# Add third y-axis for Explored % values (scaled)
ax3 = ax1.twinx()
ax3.spines["right"].set_position(("outward", 50))
ax3.plot(time_values, expl_values, label="Explored %", color=explored_colour)
# ax3.set_ylabel("Expl.%", color=explored_colour, loc="top", labelpad=10)
ax3.yaxis.label.set_rotation(0)
ax3.tick_params(axis="y", labelcolor=explored_colour)
# Add fourth y-axis for Gap % values (scaled)
ax4 = ax1.twinx()
ax4.spines["right"].set_position(("outward", 90))
ax4.plot(time_values, gap_values, label="Gap %", color=gap_colour, linestyle="--", linewidth=1)
# ax4.set_ylabel("Gap.%", color=gap_colour, loc="top", labelpad=22)
ax4.yaxis.label.set_rotation(0)
ax4.tick_params(axis="y", labelcolor=gap_colour)
# Plot vertical hash lines where Best Solution changes
for i in range(1, len(best_sol_values)):
# Print if change detected and not NaN
if (best_sol_values[i] != best_sol_values[i - 1]) and not(math.isnan(best_sol_values[i])):
ax1.axvline(x=time_values[i], color="grey", linestyle="--", linewidth=0.5)
# Shift plot area left to make room on the right for the three y-axis labels.
fig.subplots_adjust(left=0.08, right=0.85)
# Set up legend
fig.legend(loc="lower center", ncols=5)
# Show plot
plt.title("HiGHS MIP Log Analysis", pad=20)
plt.show()
#log_file_path = "/path/to/your/logfile.log"
log_file_path = "HiGHS.log"
time_values, best_bound_values, best_sol_values, in_queue_values, expl_values, gap_values = (
parse_highs_log(log_file_path)
)
plot_highs_log(
time_values, best_bound_values, best_sol_values, in_queue_values, expl_values, gap_values
)
|