Fix GLPK objective parsing#818
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
|
Hi @danielelerede-oet, I would suggest refactoring this into a classmethod and private class-attribute. I propose this: _OBJECTIVE_TOKEN: ClassVar[re.Pattern[str]] = re.compile(
r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?"
)
@classmethod
def _parse_objective(cls, text: str) -> float:
"""
Extract the objective value from a GLPK ``Objective:`` line.
GLPK reports the objective as ``<name> = <value> (MINimum)``. Anchoring
on the ``=`` drops the objective name (which may itself contain digits
or the letter ``e``) before matching the first float/scientific-notation
token, so surrounding text can no longer corrupt the parsed value.
"""
_, _, tail = text.rpartition("=")
match = cls._OBJECTIVE_TOKEN.search(tail)
if match is None:
raise ValueError(f"Could not parse objective value: {text!r}")
return float(match.group(0))
...
objective = self._parse_objective(info.Objective)And we need tests to verify it works @pytest.mark.parametrize(
"objective_text, expected",
[
# ``info.Objective`` after pd.read_csv splits off the "Objective:" key.
(" obj = 1234.56 (MINimum)", 1234.56),
(" obj = 0 (MINimum)", 0.0),
(" obj = -3.5 (MAXimum)", -3.5),
(" obj = +42 (MINimum)", 42.0),
(" obj = .5 (MINimum)", 0.5),
# scientific notation
(" obj = 1.5e+06 (MAXimum)", 1.5e6),
(" obj = -2E-3 (MINimum)", -2e-3),
# An objective name whose letters include ``e`` must not leak into the stripped number and crash float().
(" net_present_value = 1234.5 (MINimum)", 1234.5),
# robustness: a digit in the objective name must not be picked up as the value
(" obj1 = 1234.56 (MINimum)", 1234.56),
(" c2e = -7.5e2 (MAXimum)", -750.0),
# GLPK omits the "<name> =" prefix when the objective is unnamed. Fallback must work
(" 3.5 (MINimum)", 3.5),
(" -1.5e3 (MAXimum)", -1500.0),
(" 0 (MINimum)", 0.0),
],
)
def test_parse_glpk_objective(objective_text: str, expected: float) -> None:
assert GLPK._parse_objective(objective_text) == pytest.approx(expected)
@pytest.mark.parametrize(
"objective_text",
[
" obj = (MINimum)", # no value at all
" unbounded",
"",
],
)
def test_parse_glpk_objective_no_value_raises(objective_text: str) -> None:
with pytest.raises(ValueError, match="Could not parse objective value"):
GLPK._parse_objective(objective_text)Those tests cover your bug and edge cases. Feel free to add more and check if the regex works for them |
|
If you dont have the time i could push the changes to aour branch or superseed the PR. Tell me |
|
Hi @FBumann , no problem, what you asked was quite easy. BTW, I couldn't run the test file locally because |
Changes proposed in this Pull Request
While working at open-energy-transition/solver-benchmark#490 I noticed an issue in GLPK result parsing: linopy currently removes all characters except those in scientific notation and
echaracters are preserved in the surrounding text, raising a ValueError. In this PR the first valid floating-point/scientific-notation token using a regular expression is extracted and the error is now raised if no objective value can be parsed.Motivation
While benchmarking GLPK through Linopy, we encountered parsing failures caused by objective strings containing additional text. This change makes the parser robust to such cases while preserving the existing behavior for valid objective values.
Checklist
AGENTS.md).doc.doc/release_notes.rstof the upcoming release is included.