|
7 | 7 |
|
8 | 8 | import pickle |
9 | 9 | from pathlib import Path |
| 10 | +from unittest.mock import patch |
10 | 11 |
|
11 | 12 | import numpy as np |
12 | 13 | import pandas as pd |
|
15 | 16 | import xarray as xr |
16 | 17 |
|
17 | 18 | from linopy import LESS_EQUAL, Model, available_solvers, read_netcdf |
18 | | -from linopy.io import signed_number |
| 19 | +from linopy.io import _format_and_write, signed_number |
19 | 20 | from linopy.testing import assert_model_equal |
20 | 21 |
|
21 | 22 |
|
@@ -336,3 +337,48 @@ def test_to_file_lp_with_negative_zero_coefficients(tmp_path: Path) -> None: |
336 | 337 |
|
337 | 338 | # Verify Gurobi can read it without errors |
338 | 339 | gurobipy.read(str(fn)) |
| 340 | + |
| 341 | + |
| 342 | +def test_format_and_write_streaming_fallback(tmp_path): |
| 343 | + """Test that _format_and_write falls back to eager when streaming fails.""" |
| 344 | + df = pl.DataFrame({"a": ["x", "y"], "b": ["1", "2"]}) |
| 345 | + columns = [pl.col("a"), pl.lit(" "), pl.col("b")] |
| 346 | + |
| 347 | + # Normal path |
| 348 | + fn1 = tmp_path / "normal.lp" |
| 349 | + with open(fn1, "wb") as f: |
| 350 | + _format_and_write(df, columns, f) |
| 351 | + content_normal = fn1.read_text() |
| 352 | + |
| 353 | + # Force streaming to fail |
| 354 | + original_collect = pl.LazyFrame.collect |
| 355 | + |
| 356 | + def failing_collect(self, *args, **kwargs): |
| 357 | + if kwargs.get("engine") == "streaming": |
| 358 | + raise RuntimeError("simulated streaming failure") |
| 359 | + return original_collect(self, *args, **kwargs) |
| 360 | + |
| 361 | + fn2 = tmp_path / "fallback.lp" |
| 362 | + with patch.object(pl.LazyFrame, "collect", failing_collect): |
| 363 | + with open(fn2, "wb") as f: |
| 364 | + _format_and_write(df, columns, f) |
| 365 | + content_fallback = fn2.read_text() |
| 366 | + |
| 367 | + assert content_normal == content_fallback |
| 368 | + |
| 369 | + |
| 370 | +def test_to_file_lp_same_sign_constraints(tmp_path): |
| 371 | + """Test LP writing when all constraints have the same sign operator.""" |
| 372 | + m = Model() |
| 373 | + N = np.arange(5) |
| 374 | + x = m.add_variables(coords=[N], name="x") |
| 375 | + # All constraints use <= |
| 376 | + m.add_constraints(x <= 10, name="upper") |
| 377 | + m.add_constraints(x <= 20, name="upper2") |
| 378 | + m.add_objective(x.sum()) |
| 379 | + |
| 380 | + fn = tmp_path / "same_sign.lp" |
| 381 | + m.to_file(fn) |
| 382 | + content = fn.read_text() |
| 383 | + assert "s.t." in content |
| 384 | + assert "<=" in content |
0 commit comments