Repository: Gurobi/gurobi-logtools

Issue:
Memory limit
It seems runs ending with status code `MEM_LIMIT` are reported as `OPTIMAL`. In general, not all [official status codes](https://docs.gurobi.com/projects/optimizer/en/current/reference/numericcodes/statuscodes.html) are included in `termination.py`. I will work on this in a fork.

Hints from issue comments:

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 53e1b05..890ef11 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 # CHANGELOG
 ## Unreleased
 ### Fixed
+- Logs ending with memory or work limit reached, will now return a corresponding status
 ### Changed
 - If logfiles are not found when parsing then a FileNotFoundError is raised
 ### Removed
diff --git a/src/gurobi_logtools/parsers/termination.py b/src/gurobi_logtools/parsers/termination.py
index e1aa5d7..fb52695 100644
--- a/src/gurobi_logtools/parsers/termination.py
+++ b/src/gurobi_logtools/parsers/termination.py
@@ -31,6 +31,8 @@ class TerminationParser:
         re.compile(
             r"Thread count was (?P<Threads>\d+) \(of (?P<Cores>\d+) available processors\)"
         ),
+        re.compile(r"(?P<WORK_LIMIT>Work limit reached)"),
+        re.compile(r"(?P<MEM_LIMIT>Memory limit reached)"),
     ]
 
     status = [
@@ -45,6 +47,8 @@ class TerminationParser:
         "CUTOFF",
         "USER_OBJ_LIMIT",
         "INTERRUPTED",
+        "MEM_LIMIT",
+        "WORK_LIMIT",
     ]
 
     def __init__(self):


Test patch:
diff --git a/tests/parsers/test_termination.py b/tests/parsers/test_termination.py
index 6f3f469..38cb780 100644
--- a/tests/parsers/test_termination.py
+++ b/tests/parsers/test_termination.py
@@ -44,6 +44,40 @@ interrupted_summary = {
     "ErrorCode": None,
 }
 
+memlimit_log = """
+Explored 66258 nodes (470922 simplex iterations) in 18.39 seconds (16.82 work units)
+Thread count was 8 (of 8 available processors)
+
+Solution count 10: 1.50001e+09 1.50001e+09 1.50001e+09 ... 1.66668e+09
+
+Memory limit reached
+Best objective 1.500012100000e+09, best bound 1.000006566284e+09, gap 33.3334%
+"""
+
+memlimit_summary = {
+    "Status": "MEM_LIMIT",
+    "SolCount": 10,
+    "Threads": 8,
+    "Cores": 8,
+}
+
+worklimit_log = """
+Explored 30881 nodes (135045 simplex iterations) in 19.60 seconds (10.00 work units)
+Thread count was 8 (of 8 available processors)
+
+Solution count 10: 1.50001e+09 1.50001e+09 1.50001e+09 ... 1.66668e+09
+
+Work limit reached
+Best objective 1.500012100000e+09, best bound 8.997791698421e+08, gap 40.0152%
+"""
+
+worklimit_summary = {
+    "Status": "WORK_LIMIT",
+    "SolCount": 10,
+    "Threads": 8,
+    "Cores": 8,
+}
+
 
 class TestTermination(TestCase):
     def setUp(self):
@@ -61,6 +95,16 @@ class TestTermination(TestCase):
         parse_block(parser, interrupted_log)
         self.assertEqual(parser.get_summary(), interrupted_summary)
 
+    def test_memlimit(self):
+        parser = TerminationParser()
+        parse_block(parser, memlimit_log)
+        self.assertEqual(parser.get_summary(), memlimit_summary)
+
+    def test_worklimit(self):
+        parser = TerminationParser()
+        parse_block(parser, worklimit_log)
+        self.assertEqual(parser.get_summary(), worklimit_summary)
+
 
 if __name__ == "__main__":
     main()