Skip to content

Conversation

@FBumann
Copy link
Contributor

@FBumann FBumann commented Jan 3, 2026

Closes #541

Changes proposed in this Pull Request

Bug: LP file writer produced invalid syntax +-0.0 when bounds/coefficients were -0.0, causing Gurobi to reject the file.

Root cause: -0.0 >= 0 is True, so a + prefix was added, but str(-0.0) is "-0.0", producing +-0.0.

Fix: Normalize -0.0 to +0.0 using when(abs(x) == 0).then(0.0).otherwise(x) before formatting.

Performance: ~4% overhead on 10M rows

Changes:

  • linopy/io.py: Added signed_number_expr() helper, applied to 4 locations
  • test/test_io.py: Added 7 test cases

Benchmark script

#!/usr/bin/env python3
"""
Benchmark script for LP file writing performance.

Can be used ti check the impact of the negative zero fix on LP file generation speed.
"""

import tempfile
import time
from pathlib import Path

import numpy as np

from linopy import Model


def create_model(n: int) -> Model:
    """Create a test model with n^2 variables."""
    m = Model()
    N = np.arange(n)
    x = m.add_variables(coords=[N, N], name="x")
    y = m.add_variables(coords=[N, N], name="y")
    m.add_constraints(x - y >= N, name="c1")
    m.add_constraints(x + y >= 0, name="c2")
    m.add_objective((2 * x).sum() + y.sum())
    return m


def benchmark_lp_writing(sizes: list[int], iterations: int = 10) -> None:
    """Benchmark LP file writing for different model sizes."""
    print(f"Benchmarking LP file writing ({iterations} iterations each)")
    print("-" * 60)

    with tempfile.TemporaryDirectory() as tmpdir:
        for n in sizes:
            m = create_model(n)
            nvars = m.nvars
            ncons = m.ncons

            # Warmup
            fn = Path(tmpdir) / f"warmup_{n}.lp"
            m.to_file(fn, progress=False)

            # Benchmark
            times = []
            for i in range(iterations):
                fn = Path(tmpdir) / f"bench_{n}_{i}.lp"
                start = time.perf_counter()
                m.to_file(fn, progress=False)
                elapsed = time.perf_counter() - start
                times.append(elapsed)

            avg = np.mean(times)
            std = np.std(times)
            print(
                f"N={n:4d} ({nvars:>8,} vars, {ncons:>8,} cons): "
                f"{avg*1000:7.1f}ms ± {std*1000:5.1f}ms"
            )


if __name__ == "__main__":
    # Test with various model sizes
    sizes = [50, 100, 200, 500, 1000]
    benchmark_lp_writing(sizes, iterations=10)

Checklist

  • Code changes are sufficiently documented; i.e. new functions contain docstrings and further explanations may be given in doc.
  • Unit tests for new features were added (if applicable).
  • A note for the release notes doc/release_notes.rst of the upcoming release is included.
  • I consent to the release of this PR's code under the MIT license.

@FBumann FBumann marked this pull request as ready for review January 3, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: lp-file error: Unrecognized variable bound

1 participant