def hpg_hopf_eigendiagram():
state_vars = list(var("H, P, G"))
n = var("n")
vectorfield(H, P, G) = (1/(1 + G^n) - 0.2*H, H - 0.2*P, P - 0.2*G)
fig = plotly.subplots.make_subplots(rows=int(1), cols=int(2), specs=[[{"type": "scene"}, {}]])
fig = plotly.graph_objects.FigureWidget(fig)
fig.layout.margin.update(t=40, b=0, l=0, r=0)
fig.layout.scene.domain.x = [0.00, 0.70]
fig.layout.scene.domain.y = [0.00, 0.95]
fig.layout.xaxis.domain = [0.75, 1.00]
fig.layout.yaxis.domain = [0.18, 0.51]
fig.layout.showlegend = False
fig.layout.scene.xaxis.title.text = "H (GnRH)"
fig.layout.scene.yaxis.title.text = "P (FSH and LH)"
fig.layout.scene.zaxis.title.text = "G (estrogen)"
fig.layout.xaxis.title.text = "Real"
fig.layout.yaxis.title.text = "Imaginary"
fig.layout.xaxis.title.standoff = 5
fig.layout.yaxis.title.standoff = 0
fig.layout.xaxis.range = [-0.9, 0.9]
fig.layout.yaxis.range = [-0.5, 0.5]
fig.add_annotation(plotly_text("Trajectory", (0.35, 0.97), paper=True, font_size=18, xanchor="center"))
fig.add_annotation(plotly_text("Equilibrium point:", (0.72, 0.97), paper=True, font_size=18))
fig.add_annotation(plotly_text("", (0.76, 0.92), paper=True, font_size=14))
fig.add_annotation(plotly_text("Jacobian:", (0.72, 0.82), paper=True, font_size=18))
fig.add_annotation(plotly_text("", (0.76, 0.65), paper=True, font_size=14))
fig.add_annotation(plotly_text("Eigenvalues:", (0.72, 0.53), paper=True, font_size=18))
fig.add_annotation(plotly_text("", (0.74, 0.00), paper=True, font_size=14))
l1, l2, eqpt_label, l3, jacobian_label, l4, eigenvalues_label = fig.layout.annotations
fig.add_scatter3d(row=int(1), col=int(1), mode="markers", marker_color="purple", marker_size=3)
fig.add_scatter3d(row=int(1), col=int(1), mode="lines", line_color="red")
fig.add_scatter3d(row=int(1), col=int(1), mode="lines", line_color="fuchsia")
fig.add_scatter3d(row=int(1), col=int(1), mode="lines", line_color="purple", line_width=10)
fig.add_scatter(row=int(1), col=int(2), mode="markers", marker_color="red")
eqpt, trajectory1, trajectory2, attractor, eigenvalues = fig.data
@interact(n=slider(1, 20, 1, default=4))
def update(n):
field = vectorfield.subs(n=n)
G0 = find_root(1/(1 + G^n) - 0.008*G, 0, 100)
P0 = 0.2*G0
H0 = 0.2*P0
J = matrix(RDF, jacobian(field, state_vars)(H0, P0, G0))
e1, e2, e3 = J.eigenvalues()
realpart = e1.real() if abs(e1.imag()) > 1e-10 else e2.real()
initial_state = (0.5, 1, 1)
t_range = srange(0, 400, 0.05)
spiralin = desolve_odeint(field, initial_state, t_range, state_vars)
if realpart > 0:
spiralout = desolve_odeint(field, (H0 + 0.005, P0 + 0.02, G0 + 0.01), t_range, state_vars)
with fig.batch_update():
eqpt_label.text = fr"$({float(H0):.04f}, {float(P0):.04f}, {float(G0):.04f})$"
jacobian_label.text = fr"${latex(J.round(3))}$"
eigenvalues_label.text = fr"${latex_eigenvalues(J)}$"
eigenvalues.x = [a.real() for a in (e1, e2, e3)]
eigenvalues.y = [a.imag() for a in (e1, e2, e3)]
eqpt.x, eqpt.y, eqpt.z = [H0], [P0], [G0]
end = len(spiralin) - (1000 if realpart > 0 else 0)
trajectory1.x = spiralin[:end,0]
trajectory1.y = spiralin[:end,1]
trajectory1.z = spiralin[:end,2]
if realpart > 0:
attractor.x = spiralin[end:,0]
attractor.y = spiralin[end:,1]
attractor.z = spiralin[end:,2]
trajectory2.x = spiralout[:,0]
trajectory2.y = spiralout[:,1]
trajectory2.z = spiralout[:,2]
trajectory2.visible = (realpart > 0)
attractor.visible = (realpart > 0)
return fig
hpg_hopf_eigendiagram()