15 Homotopy Lab
Experiment with your own homotopies.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| echo: false
#| height: 2100px
import numpy as np
import plotly.graph_objects as go
from shiny import reactive, render
from shiny.express import ui, input
# usual abbreviations
pi = np.pi
e = np.e
sin = np.sin
cos = np.cos
tan = np.tan
ln = np.log
exp = np.exp
log = ln
default_f = "20*sin(x) - 5"
default_df = "20*cos(x)"
default_g = "x^3 - 4*x"
default_dg = "3*x^2 - 4"
default_root = "2"
default_xmin = "-0.5"
default_xmax = "2.5"
# styling
with 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;}
.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; }
}
.main-svg {
background: none !important;
}
.plotly-graph-div {
margin: auto;
}
.updatemenu-button {
transform: translate(50%, 8%);
}
.updatemenu-item-rect {
fill: #99CBFF !important;
width: 5em;
height: 2em;
x: -2.5em;
y: -1em;
}
.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;}
"""
)
ui.page_opts(full_width=True)
with ui.layout_columns(col_widths=(-1,5,5,-1)):
with ui.card(fill=False):
ui.card_header("target function")
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>""")
with ui.card():
ui.card_header("simple function")
ui.input_text("g_in", "$g(x)$", default_g)
ui.HTML("""<script> katex.render("g(x)", window['g_in-label']);</script>""")
ui.input_text("dg_in", "$g'(x)$", default_dg)
ui.HTML("""<script> katex.render("g'(x)", window['dg_in-label']);</script>""")
ui.input_text("g_root", "root of $g(x)$", default_root)
ui.HTML(r"""<script> katex.render("\\text{root of\\ } g(x)", window['g_root-label']);</script>""")
with ui.layout_columns(col_widths=(-1,5,5,-1)):
with ui.card(fill=False):
ui.card_header("method options")
ui.input_text("N_in", "Number of EM steps:", "10")
ui.input_text("n_in", "Number of NM steps:", "1")
with ui.card():
ui.card_header("rendering options")
ui.input_text("aN_in", "EM steps for actual path", "40")
ui.input_text("an_in", "NM steps for actual path", "2")
ui.input_text("xmin_in", "minimum x for plotting", default_xmin)
ui.input_text("xmax_in", "maximum x for plotting", default_xmax)
ui.input_text("dpi", "number of points for plotting the surface", "100")
with ui.layout_columns(col_widths=(-4,4,-4)):
ui.input_action_button("run_button", "Run")
def path_correction_step(x,t,H,dHdx):
return t, x - H(x,t)/dHdx(x,t)
def euler_step(x,t,dx,dt):
return t+dt, x + dt*dx(x,t)
def HM_list(x0,N,n,H,dHdx,dx):
ts = [0]
xs = [x0]
dt = float(1/N) # not strictly necessary in python
for k in range(0,N):
t,x = ts[-1], xs[-1]
T,X = euler_step(x,t,dx,dt)
ts += [T]
xs += [X]
for j in range(0,n):
T,X = path_correction_step(X,T,H,dHdx)
ts += [T]
xs += [X]
return ts,xs
@render.ui()
@reactive.event(input.run_button,ignore_none=False)
def homotopy_method():
try:
def f(x):
return eval(input.f_in().replace("^","**"))
f(0)
except:
return ui.HTML("""<p>There appears to have been an issue processing your
<span id="err">x</span>.</p>
<script> katex.render("f(x)", err);</script>
""")
try:
def df(x):
return eval(input.df_in().replace("^","**"))
df(0)
except:
return ui.HTML("""<p>There appears to have been an issue processing your
<span id="err">x</span>.</p>
<script> katex.render("f'(x)", err);</script>
""")
try:
def g(x):
return eval(input.g_in().replace("^","**"))
g(0)
except:
return ui.HTML("""<p>There appears to have been an issue processing your
<span id="err">x</span>.</p>
<script> katex.render("g(x)", err);</script>
""")
try:
def dg(x):
return eval(input.dg_in().replace("^","**"))
dg(0)
except:
return ui.HTML("""<p>There appears to have been an issue processing your
<span id="err">x</span>.</p>
<script> katex.render("g'(x)", err);</script>
""")
try:
x0 = eval(input.g_root())
except:
return ui.HTML("""<p>There appears to have been an issue processing the root of
<span id="err">x</span>.</p>
<script> katex.render("g(x)", err);</script>
""")
try:
N = eval(input.N_in())
except:
return ui.HTML("""<p>There appears to have been an issue processing number of EM steps.</p>""")
try:
n = eval(input.n_in())
except:
return ui.HTML("""<p>There appears to have been an issue processing number of NM steps.</p>""")
try:
aN = eval(input.aN_in())
except:
return ui.HTML("""<p>There appears to have been an issue processing number of EM steps for the actual path.</p>""")
try:
an = eval(input.an_in())
except:
return ui.HTML("""<p>There appears to have been an issue processing number of NM steps for the actual path.</p>""")
try:
xmin = eval(input.xmin_in())
except:
return ui.HTML("""<p>There appears to have been an issue processing minimum x for your plot.</p>""")
try:
xmax = eval(input.xmax_in())
except:
return ui.HTML("""<p>There appears to have been an issue processing minimum x for your plot.</p>""")
try:
dpi = eval(input.dpi())
except:
return ui.HTML("""<p>There appears to have been an issue processing number of points to use for plotting the surface.</p>""")
# one million input checks later, we resume.
def H(x,t):
return t*f(x) + (1-t)*g(x)
def dHdx(x,t):
return t*df(x) + (1-t)*dg(x)#
def dHdt(x,t):
return f(x) - g(x)
def dx(x,t):
return -dHdt(x,t)/dHdx(x,t)
# wahoo
Ts, Xs = HM_list(x0,N,n,H,dHdx,dx)
ts, xs = HM_list(x0,aN,an,H,dHdx,dx)
ts = ts[::an+1]
xs = xs[::an+1]
# colors
color_bg = "#C8E0FF"
color_root = '#33FF99'
color_est = '#3399FF'
color_button_border = '#0054A9'
color_button_bg = "#99CBFF"
color_fun = "#0054A9"
color_homotopy = '#3399FF'
pt_color = "#0054A9"
line_thickness = 8
marker_radius = 8
width_slice = 16
width_root = 16
xsp = np.linspace(xmin,xmax,dpi)
tsp = np.linspace(0,1,dpi)
homotopy_surface = go.Surface(
name="homotopy",
colorscale=[[0,color_homotopy], [1,color_homotopy]],
cmid=0,
showscale=False,
x = xsp,
y = tsp,
z = [[H(x,t) for x in xsp] for t in tsp])
f_slice = go.Scatter3d(
name="f(x)=H(x,0)",
mode="lines",
line={"color":color_fun, "width":width_slice},
x = xsp,
y = [0 for _ in xsp],
z = [H(x,0) for x in xsp])
g_slice = go.Scatter3d(
name="g(x)=H(x,1)",
mode="lines",
line={"color":color_fun, "width":width_slice},
x = xsp,
y = [1 for _ in xsp],
z = [H(x,1) for x in xsp])
root_path = go.Scatter3d(
name="H(x,t) = 0",
mode="lines",
line={"color":color_root, "width":width_root},
x = xs,
y = ts,
z = len(ts)*[0])
surface_fig = go.Figure(data = [homotopy_surface, f_slice, g_slice, root_path])
surface_fig.update_layout(
scene = {
"xaxis" : {"title":"x",
"color":"black",
"gridcolor":"black",
"zeroline":False,
"backgroundcolor":color_bg},
"yaxis" : {"title": "t",
"color":"black",
"gridcolor":"black",
"zeroline":False,
"backgroundcolor":color_bg},
"zaxis" : {"title": "H(x,t)",
"color":"black",
"showgrid":False,
"zeroline":False,
"backgroundcolor":color_bg},
"camera_eye": {"x": -0.8, "y": -1.2, "z": 0.8},
"aspectratio": {"x": 1, "y": 1, "z": 0.8},
"bgcolor": color_bg
},
plot_bgcolor=color_bg,
paper_bgcolor=color_bg,
autosize=True,
height=600,
margin={"l":0,"r":0,"b":0,"t":0},
legend={"x": 0.1, "y": 0.9, "font":{"color":"black"}, "bgcolor": "rgba(255,255,255,0)"}
)
pt_count = N*(n + 1) + 1
anim_fig = go.Figure(
data = [go.Scatter(x=ts,
y=xs,
name="path from root",
mode="lines",
line={"color":color_root,
"width": line_thickness}),
go.Scatter(x=(pt_count)*[0],
y=(pt_count)*[x0],
name="estimate",
mode="markers",
marker={"color":pt_color, "size":10})])
anim_fig.update_xaxes(linewidth=1,linecolor="black",zeroline=False)
anim_fig.update_yaxes(linewidth=1,linecolor="black",zeroline=False)
anim_fig.update_layout({"xaxis": {"title": "t",
"color": "black",
"gridcolor": "black",
"range": [-0.05,1.05],
"autorange": False},
"yaxis": {"title":"x",
"color": "black",
"gridcolor": "black",
"range": [xmin,xmax]}},
plot_bgcolor=color_bg,
paper_bgcolor=color_bg,
height=700,
modebar={},
margin={"l":0,"r":0,"b":0,"t":50},
legend={"x": 0.05,
"y": 0.95,
"font": {"color": "black"},
"bgcolor": color_bg},
updatemenus = [{"type": "buttons",
"buttons": [{"args": [None,
{"frame": {"duration": 400},
"fromcurrent": True,
"transition": {"duration": 400}}],
"label": "Play",
"method": "animate"
}],
"font": {"color": "black"},
"pad": {"l":0,"r":0,"b":0,"t":50},
"bordercolor": color_button_border,
"x": 0.5,
"xanchor": "center",
"y": 1.1,
"yanchor": "middle"
}]
)
anim_fig.update(frames=[go.Frame(traces=[1],
data=[go.Scatter(x=[Ts[j] for j in range(0,k)] + (pt_count-k+1)*[Ts[k]],
y=[Xs[j] for j in range(0,k)] + (pt_count-k+1)*[Xs[k]],
name="estimate",
mode="markers")]) for k in range(0,pt_count)]
)
return ui.HTML(surface_fig.to_html(full_html=False)), ui.HTML(anim_fig.to_html(full_html=False, auto_play=False))