-
Notifications
You must be signed in to change notification settings - Fork 280
Expand file tree
/
Copy pathrealtime_trace_jsonl.py
More file actions
135 lines (110 loc) · 4.11 KB
/
realtime_trace_jsonl.py
File metadata and controls
135 lines (110 loc) · 4.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import json
from pyscipopt import SCIP_EVENTTYPE, Eventhdlr
class _TraceRun:
"""
Record optimization progress in real time while the solver is running.
Args
----
model: pyscipopt.Model
path: str | None
- None: in-memory only
- str : also write JSONL (one JSON object per line) for streaming/real-time consumption
Returns
-------
None
Updates `model.data["trace"]` as a side effect.
Usage
-----
optimizeTrace(model) # real-time in-memory trace
optimizeTrace(model, path="trace.jsonl") # real-time JSONL stream + in-memory
optimizeNogilTrace(model, path="trace.jsonl") # nogil variant
"""
def __init__(self, model, path=None):
self.model = model
self.path = path
self._fh = None
self._handler = None
self._caught_events = set()
self._last_snapshot = {}
def __enter__(self):
if not hasattr(self.model, "data") or self.model.data is None:
self.model.data = {}
self.model.data["trace"] = []
if self.path is not None:
self._fh = open(self.path, "w")
class _TraceEventhdlr(Eventhdlr):
def eventinit(hdlr):
for et in (
SCIP_EVENTTYPE.BESTSOLFOUND,
SCIP_EVENTTYPE.DUALBOUNDIMPROVED,
):
self.model.catchEvent(et, hdlr)
self._caught_events.add(et)
def eventexec(hdlr, event):
et = event.getType()
if et == SCIP_EVENTTYPE.BESTSOLFOUND:
snapshot = self._snapshot_now()
self._last_snapshot = snapshot
self._write_event("bestsol_found", fields=snapshot, flush=True)
elif et == SCIP_EVENTTYPE.DUALBOUNDIMPROVED:
snapshot = self._snapshot_now()
self._last_snapshot = snapshot
# Flush disabled: frequent event; OS buffering suffices
self._write_event(
"dualbound_improved", fields=snapshot, flush=False
)
self._handler = _TraceEventhdlr()
self.model.includeEventhdlr(
self._handler, "realtime_trace_jsonl", "Realtime trace jsonl handler"
)
return self
def __exit__(self, exc_type, exc, tb):
fields = {}
if self._last_snapshot:
fields.update(self._last_snapshot)
if exc_type is not None:
fields.update(
{
"status": "exception",
"exception": exc_type.__name__,
"message": str(exc) if exc is not None else None,
}
)
try:
self._write_event("run_end", fields=fields, flush=True)
finally:
if self._fh is not None:
try:
self._fh.close()
finally:
self._fh = None
if self._handler is not None:
for et in self._caught_events:
self.model.dropEvent(et, self._handler)
self._caught_events.clear()
self._handler = None
return False
def _snapshot_now(self) -> dict:
return {
"time": self.model.getSolvingTime(),
"primalbound": self.model.getPrimalbound(),
"dualbound": self.model.getDualbound(),
"gap": self.model.getGap(),
"nodes": self.model.getNNodes(),
"nsol": self.model.getNSols(),
}
def _write_event(self, event_type, fields=None, flush=True):
event = {"type": event_type}
if fields:
event.update(fields)
self.model.data["trace"].append(event)
if self._fh is not None:
self._fh.write(json.dumps(event) + "\n")
if flush:
self._fh.flush()
def optimizeTrace(model, path=None):
with _TraceRun(model, path):
model.optimize()
def optimizeNogilTrace(model, path=None):
with _TraceRun(model, path):
model.optimizeNogil()