-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathapp.py
147 lines (140 loc) · 7.56 KB
/
app.py
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
136
137
138
139
140
141
142
143
144
145
146
147
"""
This file is the main entry point for the Streamlit app.
Further parts of the app are in the `_app` folder.
The logic for parsing the log is in the `cpsat_log_parser` folder.
"""
import streamlit as st
from cpsat_log_parser import LogParser
from cpsat_log_parser.blocks import (
SearchProgressBlock,
SearchStatsBlock,
SolutionsBlock,
TableBlock,
SolverBlock,
ResponseBlock,
PresolveLogBlock,
TaskTimingBlock,
PresolvedModelBlock,
)
from _app import print_header, input_log, show_overview
print_header()
if data := input_log():
st.header("Log Analysis")
st.warning(
"This is just a prototype and may crash or show wrong results. Please report any issues [here](https://github.com/d-krupke/CP-SAT-Log-Analyzer). I welcome any feedback and complex logs to test this on."
)
parser = LogParser(data)
show_overview(parser)
st.markdown("*You can expand the following block to see the raw log.*")
with st.expander("Raw Log"):
st.text(data)
st.markdown(
"*The following part contains a parsed version of the log, easier for analysis. Depending on the CP-SAT version, not all parts may be parsed properly.*"
)
for block in parser.blocks:
try:
if isinstance(block, SearchProgressBlock):
st.subheader("Search", divider=True)
with st.expander(block.get_title(), expanded=True):
if block.get_help():
st.info(block.get_help())
st.text(str(block))
if fig := block.as_plotly():
st.plotly_chart(fig, use_container_width=True)
st.info(
"This plot shows you how the quality of the solution (objective), and the proved quality (bound) converge over time. It allows you to estimate if finding good solutions or proving optimality is the bottleneck."
)
if fig_3 := block.gap_as_plotly():
st.plotly_chart(fig_3, use_container_width=True)
st.info(
"This plot shows you how the gap between the objective and the bound changes over time. If it quickly reaches a small value but then does not improve for a long time, you could set the `relative_gap_limit` parameter to allow to stop the search as soon as a specific solution quality is reached.\n\n The Final Gap is comparing the objective to the final bound, only known at the end. If it falls significantly faster than the Relative Gap, you could think about stopping the search earlier via a time limit, as the solution quality seems to improve quickly but proving the quality is slow."
)
if fig_2 := block.model_changes_as_plotly():
st.plotly_chart(fig_2, use_container_width=True)
st.info(
"This plot shows you how the size of the model changes over time."
)
st.subheader("Statistics", divider=True)
st.info(
"This part contains detailed statistics about the search. Only a few elements are useful for the common user."
)
elif isinstance(block, SolverBlock):
st.subheader("Initialization", divider=True)
st.info(
"This block contains some basic information about the solver and the model. For example, you can check how large the model is which parameters were changed."
)
with st.expander(block.get_title()):
if block.get_help():
st.info(block.get_help())
st.text(str(block))
elif isinstance(block, SearchStatsBlock):
with st.expander(block.get_title()):
if block.get_help():
st.info(block.get_help())
df = block.to_pandas()
st.dataframe(
df,
column_config={
"Restarts": st.column_config.NumberColumn(
help="Restarting the search once we learned about the importance of variables can significantly reduce the size of the search tree."
),
},
)
elif isinstance(block, TaskTimingBlock):
with st.expander(block.get_title()):
if block.get_help():
st.info(block.get_help())
tab1, tab2 = st.tabs(["Table", "Raw"])
df_1 = block.to_pandas(deterministic=False)
tab1.dataframe(df_1, use_container_width=True)
df_2 = block.to_pandas(deterministic=True)
tab1.dataframe(df_2, use_container_width=True)
tab2.text(str(block))
elif isinstance(block, SolutionsBlock):
with st.expander(block.get_title()):
if block.get_help():
st.info(block.get_help())
st.markdown(f"Number of solutions: {block.get_num_solutions()}")
df = block.to_pandas()
st.dataframe(df, use_container_width=True)
elif isinstance(block, PresolvedModelBlock):
with st.expander(block.get_title(), expanded=True):
if block.get_help():
st.info(block.get_help())
st.text(str(block))
elif isinstance(block, ResponseBlock):
st.subheader("Summary", divider=True)
with st.expander(block.get_title(), expanded=True):
if block.get_help():
st.info(block.get_help())
df = block.to_pandas()
st.dataframe(df.transpose(), use_container_width=True)
elif isinstance(block, PresolveLogBlock):
st.subheader("Presolve", divider=True)
st.info(
"This block contains information about the presolve phase, which is the first step of the solver. It tries to simplify the model before the actual search starts. This can significantly reduce the size of the search tree."
)
with st.expander(block.get_title()):
if block.get_help():
st.info(block.get_help())
st.text(str(block))
elif isinstance(block, TableBlock):
with st.expander(block.get_title()):
if block.get_help():
st.info(block.get_help())
tab1, tab2 = st.tabs(["Table", "Raw"])
assert isinstance(block, TableBlock), "Should be a TableBlock."
df = block.to_pandas()
tab1.dataframe(df, use_container_width=True)
tab2.text(str(block))
else:
with st.expander(block.get_title()):
if block.get_help():
st.info(block.get_help())
st.text(str(block))
except Exception as e:
st.error(
f"Could not render block `{block.get_title()}` of type `{type(block)}`. Error: '{type(e)}' - '{e}'. Please check the raw log. If you think this is a bug, please report it [here](https://github.com/d-krupke/CP-SAT-Log-Analyzer/issues)."
)
with st.expander("Raw Block", expanded=True):
st.text(str(block))