Skip to content

Commit

Permalink
add play button and improve everything for v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmilten committed Apr 11, 2021
1 parent b0662df commit eed5191
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 23 deletions.
40 changes: 30 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,39 @@

### Visual representation of the branch-and-cut tree of SCIP using spatial dissimilarities of LP solutions -- [Interactive Example](http://www.zib.de/miltenberger/treed-showcase.html)

[![Example](res/PlotlyTree.gif)](https://plot.ly/~mattmilten/103/)

## Usage:
- run `TreeD.py` to get usage information

## Dependencies:
- [PySCIPOpt](https://github.com/SCIP-Interfaces/PySCIPOpt) to solve the instance and generate the necessary tree data
- [Plot.ly](https://plot.ly/) to draw the 3D visualization
[![Example](res/treed-example.png)](https://plot.ly/~mattmilten/103/)
## Installation

```
python -m pip install treed
```

## Usage
- run Python script `bin/treed` (will be installed into your PATH on Linux/macOS when using `pip install treed`) to get usage information or use this code snippet in a Jupyter notebook:

```
from treed import TreeD
treed = TreeD(
probpath="model.mps",
nodelimit=20,
transformation='mds',
showcuts=True
)
treed.solve()
fig = treed.draw()
fig.show(renderer='notebook')
```

## Dependencies
- [PySCIPOpt](https://github.com/scipopt/PySCIPOpt) to solve the instance and generate the necessary tree data
- [Plotly](https://plot.ly/) to draw the 3D visualization
- [pandas](https://pandas.pydata.org/) to organize the collected data
- [sklearn](http://scikit-learn.org/stable/) for multi-dimensional scaling
- [pysal](https://github.com/pysal) to compute statistics based on spatial (dis)similarity
- [pysal](https://github.com/pysal) to compute statistics based on spatial (dis)similarity; this is optional

## Export to [Amira](https://amira.zib.de/):
## Export to [Amira](https://amira.zib.de/)
- run `AmiraTreeD.py` to get usage information.

`AmiraTreeD.py` generates the '.am' data files to be loaded by Amira software to draw the tree using LineRaycast.
Expand Down
2 changes: 1 addition & 1 deletion bin/treed
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ treed = TreeD(
nodelimit=args.nodelimit,
)

treed.main()
treed.solve()
treed.draw()
Binary file added res/treed-example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name="treed",
version="0.0.2",
version="1.0.0",
author="Matthias Miltenberger",
author_email="[email protected]",
description="3D Visualization of Branch-and-Cut Trees using PySCIPOpt",
Expand Down
143 changes: 132 additions & 11 deletions src/treed/treed.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,16 @@ def transform(self):
df = self.df["LPsol"].apply(pd.Series).fillna(value=0)
if self.transformation == "tsne":
mf = manifold.TSNE(n_components=2)
elif self.transformation == "lle":
mf = manifold.LocallyLinearEmbedding(n_components=2)
elif self.transformation == "ltsa":
mf = manifold.LocallyLinearEmbedding(n_components=2, method="ltsa")
elif self.transformation == "spectral":
mf = manifold.SpectralEmbedding(n_components=2)
else:
mf = manifold.MDS(n_components=2)
self.xy = mf.fit_transform(df)
self.stress = mf.stress_
# self.stress = mf.stress_ # no available with all transformations

self.df["x"] = self.xy[:, 0]
self.df["y"] = self.xy[:, 1]
Expand Down Expand Up @@ -259,13 +265,69 @@ def _create_nodes_and_projections(self):
)
return node_object, proj_object

def _create_nodes_frames(self):
colorbar = go.scatter3d.marker.ColorBar(title="", thickness=10, x=0)
marker = go.scatter3d.Marker(
symbol=self.df["symbol"],
size=self.nodesize,
color=self.df["age"],
colorscale=self.colorscale,
colorbar=colorbar,
)

frames = []
sliders_dict = dict(
active=0,
yanchor="top",
xanchor="left",
currentvalue={"prefix": "Age:", "visible": True, "xanchor": "right",},
len=0.9,
x=0.05,
y=0.1,
steps=[],
)

for a in self.df["age"]:
adf = self.df[self.df["age"] <= a]
node_object = go.Scatter3d(
x=adf["x"],
y=adf["y"],
z=adf["objval"],
mode="markers+text",
marker=marker,
hovertext=adf["number"],
hovertemplate="LP obj: %{z}<br>node number: %{hovertext}<br>%{marker.color}",
hoverinfo="z+text+name",
opacity=0.7,
name="LP solutions",
)
frames.append(go.Frame(data=node_object, name=str(a)))

slider_step = {
"args": [
[a],
{
"frame": {"redraw": True, "restyle": False},
"fromcurrent": True,
"mode": "immediate",
},
],
"label": a,
"method": "animate",
}
sliders_dict["steps"].append(slider_step)

return frames, sliders_dict

def draw(self):
"""Draw the tree, depending on the mode"""

self.transform()

nodes, nodeprojs = self._create_nodes_and_projections()

frames, sliders = self._create_nodes_frames()

edges = go.Scatter3d(
x=self.Xe,
y=self.Ye,
Expand Down Expand Up @@ -324,45 +386,104 @@ def draw(self):
scene=scene,
)

updatemenus = list(
layout["updatemenus"] = list(
[
dict(
buttons=list(
[
dict(
args=["marker.color", [self.df["age"]]],
label="Node Age",
method="restyle",
args=[
{
"marker.color": [self.df["age"]],
"marker.cauto": min(self.df["age"]),
"marker.cmax": max(self.df["age"]),
}
],
),
dict(
args=["marker.color", [self.df["depth"]]],
label="Tree Depth",
method="restyle",
args=[
{
"marker.color": [self.df["depth"]],
"marker.cauto": min(self.df["depth"]),
"marker.cmax": max(self.df["depth"]),
}
],
),
dict(
args=["marker.color", [self.df["condition"]]],
label="LP Condition (log 10)",
method="restyle",
args=[
{
"marker.color": [self.df["condition"]],
"marker.cmin": 1,
"marker.cmax": 20,
}
],
),
dict(
args=["marker.color", [self.df["iterations"]]],
label="LP Iterations",
method="restyle",
args=[
{
"marker.color": [self.df["iterations"]],
"marker.cauto": min(self.df["iterations"]),
"marker.cmax": max(self.df["iterations"]),
}
],
),
]
),
direction="down",
showactive=True,
type="buttons",
# x = 1.2,
# y = 0.6,
),
dict(
buttons=list(
[
dict(
label="▶",
method="animate",
args=[
None,
{
"frame": {
"duration": 50,
"redraw": True,
},
"fromcurrent": True,
},
],
args2=[
[None],
{
"frame": {"duration": 0, "redraw": False},
"mode": "immediate",
"transition": {"duration": 0},
},
],
)
]
),
direction="left",
yanchor="top",
xanchor="right",
showactive=True,
type="buttons",
x=0,
y=0,
),
]
)

layout["updatemenus"] = updatemenus
layout["sliders"] = [sliders]

self.fig = go.Figure(data=[nodes, nodeprojs, edges, optval], layout=layout)
self.fig = go.Figure(
data=[nodes, nodeprojs, edges, optval], layout=layout, frames=frames,
)

self.fig.write_html(file=filename, include_plotlyjs=self.include_plotlyjs)

Expand All @@ -373,7 +494,7 @@ def draw(self):

return self.fig

def main(self):
def solve(self):
"""Solve the instance and collect and generate the tree data"""

self.nodelist = []
Expand Down

0 comments on commit eed5191

Please sign in to comment.