Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 83 additions & 2 deletions infrastructure/startup-script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ echo "Generated unique run ID: ${RUN_ID}"
# Update and install packages
echo "Updating packages..."
apt-get -qq update
apt-get -qq install -y tmux git time curl jq build-essential
apt-get -qq install -y tmux git time curl jq build-essential cmake

# Set up Gurobi license
mkdir -p /opt/gurobi
gsutil cp gs://solver-benchmarks-restricted/gurobi-benchmark-40-session.lic /opt/gurobi/gurobi.lic

# Clone the repository
echo "Cloning repository..."
git clone --depth=1 -b run-v1 https://github.com/open-energy-transition/solver-benchmark.git
git clone --depth=1 -b highs-hipo-runs https://github.com/open-energy-transition/solver-benchmark.git

# Install a global highs binary for reference runs
echo "Installing Highs..."
Expand All @@ -30,6 +30,82 @@ tar -xzf HiGHSstatic.tar.gz -C /opt/highs/
chmod +x /opt/highs/bin/highs
/opt/highs/bin/highs --version

# Install additional HiGHS from hipo branch with dependencies
echo "Installing HiGHS from hipo branch with required dependencies..."

# Set up working directory
HIGHS_HIPO_DIR="/opt/highs-hipo-workspace"
mkdir -p "${HIGHS_HIPO_DIR}"
cd "${HIGHS_HIPO_DIR}"

# Install BLAS dependency
echo "Installing BLAS..."
apt-get -qq install -y libblas-dev

# 1. Clone GKLib
echo "Cloning GKLib..."
git clone https://github.com/KarypisLab/GKlib.git

# 2. Clone METIS
echo "Cloning METIS..."
git clone https://github.com/KarypisLab/METIS.git

# 3. Create installs directory
echo "Creating installs directory..."
mkdir -p installs

# 4. Install GKLib shared (using shared approach due to linking errors)
echo "Installing GKLib as shared library..."
cd GKlib
make config shared=1 prefix="${HIGHS_HIPO_DIR}/installs"
make
make install
cd "${HIGHS_HIPO_DIR}"

# Check if shared library link is needed and create it
if [ ! -f "${HIGHS_HIPO_DIR}/installs/lib/libGKlib.so" ] && [ -f "${HIGHS_HIPO_DIR}/installs/lib/libGKlib.so.0" ]; then
echo "Creating symlink for libGKlib.so..."
ln -sf "${HIGHS_HIPO_DIR}/installs/lib/libGKlib.so.0" "${HIGHS_HIPO_DIR}/installs/lib/libGKlib.so"
fi

# 5. Install METIS shared
echo "Installing METIS as shared library..."
cd METIS
make config shared=1 gklib_path="${HIGHS_HIPO_DIR}/installs" prefix="${HIGHS_HIPO_DIR}/installs"
make
make install
cd "${HIGHS_HIPO_DIR}"

# 6. Verify dependencies installation
echo "Verifying dependencies installation..."
ls -la "${HIGHS_HIPO_DIR}/installs"
ls -la "${HIGHS_HIPO_DIR}/installs/lib"

# 7. Clone and build HiGHS with hipo support
echo "Cloning HiGHS repository..."
git clone https://github.com/ERGO-Code/HiGHS.git
cd HiGHS

# Checkout the hipo branch
echo "Checking out hipo branch..."
git checkout hipo

# 8. Configure HiGHS with HIPO enabled and dependency paths
echo "Configuring HiGHS with HIPO support..."
cmake -S. -B build \
-DHIPO=ON \
-DMETIS_ROOT="${HIGHS_HIPO_DIR}/installs" \
-DGKLIB_ROOT="${HIGHS_HIPO_DIR}/installs"
cmake --build build

# Verify the installation
echo "Verifying HiGHS hipo installation..."
"${HIGHS_HIPO_DIR}/HiGHS/build/bin/highs" --version
echo "HiGHS hipo installation completed"

# Go back to root directory
cd /

# Downloading benchmark reference model
curl -L "https://storage.googleapis.com/solver-benchmarks/benchmark-test-model.lp" -o benchmark-test-model.lp

Expand All @@ -45,6 +121,11 @@ echo "Setting up conda environment..."
echo "source ~/miniconda3/bin/activate" >> ~/.bashrc
~/miniconda3/bin/conda init bash

# Accept Anaconda Terms of Service to avoid interactive prompts
echo "Accepting Anaconda Terms of Service..."
~/miniconda3/bin/conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main
~/miniconda3/bin/conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r

# Get benchmark years from instance metadata
BENCHMARK_YEARS_JSON=$(curl -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/attributes/benchmark_years")
echo "Retrieved benchmark years: ${BENCHMARK_YEARS_JSON}"
Expand Down
12 changes: 10 additions & 2 deletions runner/benchmark_all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,18 @@ for year in "${years[@]}"; do
# Run the benchmark script for the year
echo "Running benchmarks for the year: $year"
conda activate "$env_name"

# Add highs-hipo solver for 2025 benchmarks
if [ "$year" = "2025" ]; then
solver_args="--solvers highs scip cbc gurobi glpk highs-hipo"
else
solver_args="--solvers highs scip cbc gurobi glpk"
fi

if [ "$idx" -eq 0 ]; then
python "$BENCHMARK_SCRIPT" "$BENCHMARKS_FILE" "$year" $append_results --ref_bench_interval "$reference_interval" --run_id "$run_id"
python "$BENCHMARK_SCRIPT" "$BENCHMARKS_FILE" "$year" $append_results --ref_bench_interval "$reference_interval" --run_id "$run_id" $solver_args
else
python "$BENCHMARK_SCRIPT" "$BENCHMARKS_FILE" "$year" --append --ref_bench_interval "$reference_interval" --run_id "$run_id"
python "$BENCHMARK_SCRIPT" "$BENCHMARKS_FILE" "$year" --append --ref_bench_interval "$reference_interval" --run_id "$run_id" $solver_args
fi
conda deactivate

Expand Down
42 changes: 39 additions & 3 deletions runner/run_benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ def get_conda_package_versions(solvers, env_name=None):
name_to_pkg = {"highs": "highspy", "cbc": "coin-or-cbc"}
solver_versions = {}
for solver in solvers:
package = name_to_pkg.get(solver, solver)
solver_versions[solver] = installed_packages.get(package, None)
# Handle highs-hipo as a special case - not a conda package
if solver == "highs-hipo":
solver_versions[solver] = get_highs_hipo_version()
else:
package = name_to_pkg.get(solver, solver)
solver_versions[solver] = installed_packages.get(package, None)

return solver_versions

Expand Down Expand Up @@ -305,6 +309,29 @@ def get_highs_binary_version():
return "unknown"


def get_highs_hipo_version():
"""Get the version of the HiGHS-HiPO binary from the --version command"""
highs_hipo_binary = "/opt/highs-hipo-workspace/HiGHS/build/bin/highs"

try:
result = subprocess.run(
[highs_hipo_binary, "--version"],
capture_output=True,
text=True,
check=True,
encoding="utf-8",
)

version_match = re.search(r"HiGHS version (\d+\.\d+\.\d+)", result.stdout)
if version_match:
return version_match.group(1) + "-hipo"

return "unknown-hipo"
except Exception as e:
print(f"Error getting HiGHS-HiPO binary version: {str(e)}")
return "unknown-hipo"


def benchmark_highs_binary():
"""
Run a reference benchmark using the pre-installed HiGHS binary
Expand Down Expand Up @@ -456,11 +483,20 @@ def main(

for benchmark in processed_benchmarks:
for solver in solvers:
# Restrict highs-hipo to 2025 only
if solver == "highs-hipo" and year != "2025":
print(
f"Solver {solver} is only available for 2025 benchmarks. Current year: {year}. Skipping."
)
continue

solver_version = solvers_versions.get(solver)
if not solver_version:
print(f"Solver {solver} is not available. Skipping.")
continue

print(f"Found solver {solver} with version {solver_version}")

metrics = {}
runtimes = []
memory_usages = []
Expand Down Expand Up @@ -578,7 +614,7 @@ def main(
type=str,
nargs="+",
default=["highs", "scip", "cbc", "gurobi", "glpk"],
help="The list of solvers to run. Solvers not present in the active environment will be skipped.",
help="The list of solvers to run. Solvers not present in the active environment will be skipped. For 2025, highs-hipo is also available.",
)
parser.add_argument(
"--append",
Expand Down
105 changes: 105 additions & 0 deletions runner/run_solver.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import json
import subprocess
import sys
import time
from pathlib import Path
from time import perf_counter
from traceback import format_exc
Expand All @@ -16,6 +18,10 @@


def get_solver(solver_name):
# Handle highs-hipo as a special case - it doesn't use linopy
if solver_name.lower() == "highs-hipo":
return None # Signal that this solver needs special handling

try:
solver_enum = SolverName(solver_name.lower())
except ValueError:
Expand Down Expand Up @@ -134,8 +140,107 @@ def get_reported_runtime(solver_name, solver_model) -> float | None:
return None


def run_highs_hipo_solver(input_file, solver_version):
"""
Run the HiGHS-HiPO solver directly using the binary with --solver="hipo" --parallel="on"
"""
highs_hipo_binary = "/opt/highs-hipo-workspace/HiGHS/build/bin/highs"

solution_dir = Path(__file__).parent / "solutions"
solution_dir.mkdir(parents=True, exist_ok=True)

logs_dir = Path(__file__).parent / "logs"
logs_dir.mkdir(parents=True, exist_ok=True)

output_filename = f"{Path(input_file).stem}-highs-hipo-{solver_version}"
solution_fn = solution_dir / f"{output_filename}.sol"
log_fn = logs_dir / f"{output_filename}.log"

command = [
highs_hipo_binary,
"--solver=hipo",
"--parallel=on",
input_file,
f"--solution_file={solution_fn}",
]

# Run the command and capture the output
start_time = time.perf_counter()
try:
result = subprocess.run(
command,
capture_output=True,
text=True,
check=False,
encoding="utf-8",
)
runtime = time.perf_counter() - start_time

# Write stdout and stderr to log file
with open(log_fn, "w") as f:
f.write(f"Command: {' '.join(command)}\n")
f.write(f"Return code: {result.returncode}\n\n")
f.write("STDOUT:\n")
f.write(result.stdout)
f.write("\n\nSTDERR:\n")
f.write(result.stderr)

if result.returncode != 0:
return {
"runtime": runtime,
"reported_runtime": runtime,
"status": "error",
"condition": "Error",
"objective": None,
"duality_gap": None,
"max_integrality_violation": None,
}
else:
# Parse HiGHS output to extract objective value
objective = None
for line in result.stdout.splitlines():
if "Objective value" in line:
try:
objective = float(line.split(":")[-1].strip())
except (ValueError, IndexError):
pass

return {
"runtime": runtime,
"reported_runtime": runtime,
"status": "ok",
"condition": "Optimal",
"objective": objective,
"duality_gap": None, # Not available from command line output
"max_integrality_violation": None, # Not available from command line output
}
except Exception as e:
runtime = time.perf_counter() - start_time
# Write error to log file
with open(log_fn, "w") as f:
f.write(f"Command: {' '.join(command)}\n")
f.write(f"Exception: {str(e)}\n")

return {
"runtime": runtime,
"reported_runtime": runtime,
"status": "error",
"condition": "Error",
"objective": None,
"duality_gap": None,
"max_integrality_violation": None,
}


def main(solver_name, input_file, solver_version):
problem_file = Path(input_file)

# Handle highs-hipo solver separately
if solver_name.lower() == "highs-hipo":
results = run_highs_hipo_solver(input_file, solver_version)
print(json.dumps(results))
return

solver = get_solver(solver_name)

solution_dir = Path(__file__).parent / "solutions"
Expand Down
Loading