#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| echo: false
from shiny import App, ui, render, reactive
from shinywidgets import output_widget, render_widget
import matplotlib.pyplot as plt
import plotly.graph_objects as go
import numpy as np
#aaa
default_f = "x^3"
default_df = "3*x^2"
default_root = "2"
default_xmin = "-9"
default_xmax = "9"
default_ymin = "-9"
default_ymax = "9"
def monodromy_list(x0,gamma,f,df,n):
N = len(gamma)-1
dt = 1/N
dgamma = [gamma[t+1]-gamma[t] for t in range(0,N)]
xs = [x0]
for k in range(0,N):
xs += [xs[-1] + dgamma[k]/df(xs[-1])]
for j in range(0,n):
xs[-1] = xs[-1] - (f(xs[-1]) - gamma[k])/df(xs[-1])
xreals = [real(x) for x in xs]
ximags = [imag(x) for x in xs]
preals = list(map(real,gamma))
pimags = list(map(imag,gamma))
return preals,pimags,xreals,ximags
j = complex(0,1)
real = np.real
imag = np.imag
pi = np.pi
e = np.e
sin = np.sin
cos = np.cos
tan = np.tan
ln = np.log
exp = np.exp
log = ln
def monodromy_plotter(preals,pimags,xreals,ximags,style="together",lineshape="spline"):
color_bg = "#C8E0FF"
color_button_border = '#0054A9'
color_path = "#A95400"
color_lift = "#0054A9"
dot_size = 10
N = len(preals)
base_path = go.Scatter(
name="base path",
showlegend=False,
x = preals,
y = pimags,
mode="lines",
line={"shape": lineshape,
"color": color_path}
)
root_path = go.Scatter(
name="root path",
showlegend=False,
x = xreals,
y = ximags,
mode="lines",
line={"shape": lineshape,
"color": color_lift}
)
base_pt = go.Scatter(
name="base",
showlegend=False,
x = preals[0:1],
y = pimags[0:1],
mode="markers",
marker={"color":color_path,
"size": dot_size}
)
root_pt = go.Scatter(
name="root",
showlegend=False,
x = xreals[0:1],
y = ximags[0:1],
mode="markers",
marker={"color":color_lift,
"size": dot_size}
)
fig = go.Figure()
if style=="together":
fig.update(data=[base_pt,root_pt,base_path,root_path])
fig.update(frames=[go.Frame(traces=[0,1],
data=[go.Scatter(x=preals[k:k+1],y=pimags[k:k+1]),
go.Scatter(x=xreals[k:k+1],y=ximags[k:k+1])]
) for k in range(0,N+1)
]
)
fig.update_xaxes({"title": "real part",
"color": "black",
"showgrid": False,
"zerolinecolor": "black",
"range": [min(np.min(xreals),np.min(preals))-1,
max(np.max(xreals),np.max(preals))+1,],
"autorange": False})
fig.update_yaxes({"title": "imaginary part",
"color": "black",
"showgrid": False,
"zerolinecolor": "black",
"range": [min(np.min(ximags),np.min(pimags))-1,
max(np.max(ximags),np.max(pimags))+1,],
"autorange": False})
fig.update_layout(height=700)
elif style=="separate":
fig.set_subplots(rows=1,cols=2,subplot_titles=("Lifted Path", "Base Path"))
fig.add_trace(base_pt,row=1,col=2)
fig.add_trace(root_pt,row=1,col=1)
fig.add_trace(base_path,row=1,col=2)
fig.add_trace(root_path,row=1,col=1)
fig.update(frames=[go.Frame(traces=[0,1],
data=[go.Scatter(x=preals[k:k+1],y=pimags[k:k+1]),
go.Scatter(x=xreals[k:k+1],y=ximags[k:k+1])]
) for k in range(0,N+1)
]
)
fig.update_xaxes({"title": "real part",
"color": "black",
"showgrid": False,
"zerolinecolor": "black",
"range": [np.min(preals)-1,
np.max(preals)+1,],
"autorange": False},
row=1, col=2)
fig.update_yaxes({"title": "imaginary part",
"color": "black",
"showgrid": False,
"zerolinecolor": "black",
"range": [np.min(pimags)-1,
np.max(pimags)+1,],
"autorange": False},
row=1, col=2)
fig.update_xaxes({"title": "real part",
"color": "black",
"showgrid": False,
"zerolinecolor": "black",
"range": [np.min(xreals)-1,
np.max(xreals)+1,],
"autorange": False},
row=1, col=1)
fig.update_yaxes({"title": "imaginary part",
"color": "black",
"showgrid": False,
"zerolinecolor": "black",
"range": [np.min(ximags)-1,
np.max(ximags)+1,],
"autorange": False},
row=1, col=1)
fig.update_layout(height=600)
fig.update_layout(title={"font":{"color":"black"}},
plot_bgcolor=color_bg,
paper_bgcolor=color_bg,
margin={"l":0,"r":50,"b":0,"t":50},
legend={"x": 0.025,
"y": 0.975,
"font": {"color": "black"},
"bgcolor": color_bg},
updatemenus = [{"type": "buttons",
"buttons": [{"args": [None,
{"frame": {"duration": 4000/N,
"redraw": True},
"fromcurrent": False,
"transition": {"duration": 4000/N,
"easing": "linear"}}],
"label": "Animate",
"method": "animate"}],
"font": {"color": "black"},
"pad": {"l":0,"r":0,"b":0,"t":50},
"bordercolor": "rgba(0,0,0,0)",
"x": 0,
"xanchor": "left",
"y": 1,
"yanchor": "middle"
}]
)
return fig
color_bg = "#C8E0FF"
color_dark_bg = "#99CBFF"
color_root = '#0054A9'
color_path = '#A95400'
# styling
katex_and_css = ui.tags.head(
ui.tags.link(
rel="stylesheet",
href="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css"
),
ui.tags.script(src="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.js"),
ui.tags.style(
"""
body { background: #C8E0FF !important; }
.irs-bar { background: #0054A9 !important; }
.irs-handle { background: #0054A9 !important; }
.irs-single { background: #0054A9 !important; color: #FFFFFF !important; }
.irs-min { background:#99CBFF !important; color: #000000 !important; }
.irs-max { background:#99CBFF !important; color: #000000 !important; }
.control-label { display: block; text-align: center; }
.shiny-input-container { margin:auto; }
.shiny-input-select { background-color: #99CBFF; border: 1px solid #0054A9; }
.shiny-input-number { background-color: #99CBFF; border: 1px solid #0054A9; }
.shiny-input-number:focus { background-color: #99CBFF; border: 1px solid #0054A9; }
.shiny-input-text { background-color: #99CBFF; border: 1px solid #0054A9; }
.shiny-input-text:focus { background-color: #99CBFF; border: 1px solid #0054A9; }
.action-button { background-color: #99CBFF; color: black; border: none; margin: auto; display: block; }
.action-button:hover { background-color: #0054A9; color: white; border-color: none;}
.card { background: #C8E0FF; border: 1px solid #0054A9; box-shadow: none; }
.card-header { text-align: center; border-bottom: 1px solid #0054A9; }
}
.plotly-graph-div {
margin: auto;
}
.updatemenu-button {
transform: translate(50%, 95%);
}
.updatemenu-container:hover > .updatemenu-header-group > .updatemenu-button > .updatemenu-item-rect {
fill: #0054A9 !important;
}
.updatemenu-container:hover > .updatemenu-header-group > .updatemenu-button > .updatemenu-item-text {
fill: #FFFFFF !important;
}
.updatemenu-item-rect {
fill: #99CBFF !important;
width: 10em;
height: 44.5px;
x: -5em;
y: -22.25px;
}
.updatemenu-item-text {
text-anchor: middle;
dominant-baseline: central;
font-size: 0.9375rem !important;
transform: translate(-12px, -19.5px); /* evil hack */
}
.modebar-container {visibility: hidden;}
"""
)
)
j = complex(0,1)
fin_card = ui.card(
ui.card_header("function info"),
ui.input_text("f_in","f(x)",default_f),
ui.HTML("""<script> katex.render("f(x)", window['f_in-label']);</script>"""),
ui.input_text("df_in","f'(x)",default_df),
ui.HTML("""<script> katex.render("f'(x)", window['df_in-label']);</script>"""),
ui.input_text("root_in","fiber",default_root),
fill=False
)
path_dim_card = ui.card(
ui.card_header("path drawing info"),
ui.layout_columns(
ui.input_text("xmax_in","xmax",default_xmax,width="6em"),
ui.input_text("xmin_in","xmin",default_xmin,width="6em"),
ui.input_text("ymax_in","ymax",default_ymax,width="6em"),
ui.input_text("ymin_in","ymin",default_ymin,width="6em"),
col_widths={"sm": (6,6)}
),
fill=False
)
app_ui = ui.page_sidebar(
ui.sidebar(
ui.navset_pill(
ui.nav_panel("path",
ui.output_plot("draw_plot",
height='600px',
click=True,
hover=ui.hover_opts(delay=10,delay_type="throttle")),
ui.input_action_button("run_btn","Set Path",width="10em")
),
ui.nav_panel("function",
ui.layout_columns(
fin_card,
path_dim_card,
ui.input_action_button("set_btn","Set Info",width="10em"),
col_widths={"sm": (12)})
),
ui.nav_panel("about",
ui.markdown(
"""
A fun little tool to see what monodromy looks like!
There are two other tabs:
- Set the cover and fiber, plus the dimensions of the path drawing interface.
- Draw the "downstairs" path. When the background is dark, it's following your mouse.
Try drawing a loop which goes around zero, then one that doesn't. Then try other directions, or looping multiple times.
Note: it can be very slow and will not work for all display sizes. The implementation is Euler's method with one step of Newton's method path correction.
"""
)
)
),
width="30%",
position="right",
bg="#C8E0FF"
),
katex_and_css,
ui.output_ui("monodromy_plot", height='800px')
)
plot_points = [[],[]]
def server(input, output, session):
points = reactive.Value([[],[]])
tracing = reactive.Value(False)
f = reactive.Value(lambda x : x**3)
df = reactive.Value(lambda x : 3*x**2)
xmin = reactive.Value(-8.5)
xmax = reactive.Value(8.5)
ymin = reactive.Value(-8.5)
ymax = reactive.Value(8.5)
root = reactive.Value(2)
@reactive.Effect
@reactive.event(input.set_btn, ignore_none=False)
def _():
fstr = input.f_in().replace("^","**")
dfstr = input.df_in().replace("^","**")
def fs(x):
return eval(fstr)
def dfs(x):
return eval(dfstr)
f.set(fs)
df.set(dfs)
xmin.set(float(input.xmin_in()))
xmax.set(float(input.xmax_in()))
ymin.set(float(input.ymin_in()))
ymax.set(float(input.ymax_in()))
@render.plot
def draw_plot():
t = tracing.get()
fig, ax = plt.subplots()
fig.suptitle("Draw a path for the monodromy method.\n Click to start tracing, click again to stop.")
fig.set_facecolor(color_bg)
ax.set_xlim([xmin.get(),xmax.get()])
ax.set_ylim([ymin.get(),ymax.get()])
ax.set_box_aspect(1)
if t:
ax.set_facecolor(color_dark_bg)
else:
ax.set_facecolor(color_bg)
fr = f.get()(root.get())
ax.plot(real(fr),imag(fr),'o',color=color_path)
cx,cy = plot_points
ax.plot(cx, cy, '-', color=color_path)
return fig
@reactive.Effect
@reactive.event(input.draw_plot_click)
def _():
global plot_points
t = tracing.get()
if not t:
plot_points = [[input.draw_plot_hover()['x']],[input.draw_plot_hover()['y']]]
points.set(plot_points)
tracing.set(not t)
@reactive.Effect
@reactive.event(input.draw_plot_hover)
def _():
global plot_points
if tracing.get():
currx,curry = points.get()
currx += [input.draw_plot_hover()['x']]
curry += [input.draw_plot_hover()['y']]
plot_points = [currx,curry]
points.set(plot_points)
@render.ui
@reactive.event(input.run_btn)
def monodromy_plot():
with reactive.isolate():
xs,ys = points.get()
gamma = [xs[k] + j* ys[k] for k in range(0,len(xs))]
plot_html = monodromy_plotter(
*monodromy_list(root.get(),gamma,f.get(),df.get(),1),
style="separate",
lineshape="spline").to_html(full_html=False,
auto_play=False)
return ui.HTML(plot_html)
app = App(app_ui, server)