diff --git a/CHANGELOG.md b/CHANGELOG.md index 650d443..50ebfd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # CHANGELOG ## Unreleased ### Added -- Parsing of deterministic work metric in nodelog, barrier, and simplex (#27). +- Parsing of deterministic work metric in nodelog, barrier, and simplex (#27) +- Add a ChangedParams summary column with parameters as a dictionary (#21) ### Fixed -- Handle pandas warning related to groupy() +- Handle pandas warning related to groupby() ### Changed ### Removed diff --git a/src/grblogtools/parsers/header.py b/src/grblogtools/parsers/header.py index ff952f1..ce17507 100644 --- a/src/grblogtools/parsers/header.py +++ b/src/grblogtools/parsers/header.py @@ -87,3 +87,7 @@ def get_summary(self) -> dict: def get_parameters(self) -> dict: """Return all changed parameters detected in the header.""" return self._parameters + + def changed_params(self) -> int: + omit_params = {"Seed", "LogFile"} + return {k: v for k, v in self._parameters.items() if k not in omit_params} diff --git a/src/grblogtools/parsers/single_log.py b/src/grblogtools/parsers/single_log.py index f2b587e..83452e9 100644 --- a/src/grblogtools/parsers/single_log.py +++ b/src/grblogtools/parsers/single_log.py @@ -49,6 +49,7 @@ def get_summary(self): quad_nonzeros=summary.get("NumQNZs", 0), quad_constrs=summary.get("NumQConstrs", 0), ) + summary["ChangedParams"] = self.header_parser.changed_params() return summary def parse(self, line: str) -> bool: diff --git a/tests/parsers/test_header.py b/tests/parsers/test_header.py index 5c9451e..00a16b6 100644 --- a/tests/parsers/test_header.py +++ b/tests/parsers/test_header.py @@ -139,6 +139,16 @@ def test_tuner_log(self): parse_lines(parser, ["Solving model misc07"]) assert parser.get_summary() == {"ModelName": "misc07"} + def test_changed_params(self): + """Test non-default algorithm parameters (ignore seed and logfile)""" + parser = HeaderParser() + parser.parse("Set parameter Method to value 2") + parser.parse("Set parameter Threads to value 4") + parser.parse("Set parameter Seed to value 238476") + parser.parse("Set parameter LogFile to value log.log") + parser.parse("Set parameter TimeLimit to value 60") + assert parser.changed_params() == {"Method": 2, "Threads": 4, "TimeLimit": 60} + if __name__ == "__main__": main() diff --git a/tests/test_api.py b/tests/test_api.py index 54e33d2..b3524f6 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -187,8 +187,45 @@ def test_work(): summary = result.summary() # Check if work column present - assert len(summary.columns) == 88 assert set(summary.columns).issuperset({"Work"}) # Check if Runtime and Work found assert summary["Work"].count() == 6 + + +def test_changed_params(): + result = glt.parse("tests/assets/*.log") + summary = result.summary() + assert set(summary.columns).issuperset({"ChangedParams"}) + assert summary["ChangedParams"].apply(lambda d: isinstance(d, dict)).all() + assert summary["ChangedParams"].count() == 7 + + +def test_create_label(): + # Test expected workflow for creating custom labels + + def label(row): + params = row["ChangedParams"] + if params: + paramstr = "-".join( + f"{k}={v}" for k, v in sorted(params.items()) if k != "TimeLimit" + ) + else: + paramstr = "Default" + version = row["Version"].replace(".", "") + return f"{version}-{paramstr}" + + summary = ( + glt.parse("tests/assets/*.log") + .summary() + .assign(Label=lambda df: df.apply(label, axis="columns")) + ) + assert set(summary["Label"]) == { + "950-CrossoverBasis=1-Method=2", + "950-Default", + "950-Method=0", + "950-Method=2", + "950-Method=3", + "950-NonConvex=2", + "912-NoRelHeurWork=60", + }