mirror of
https://github.com/bics-rug/felice-models.git
synced 2026-09-30 21:03:06 +02:00
Initial commit
Co-authored-by: Aradhana Dube <a.dube@rug.nl> Co-authored-by: Renzo I. Barraza Altamirano <r.i.barraza.altamirano@rug.nl> Co-authored-by: Paolo Gibertini <p.gibertini@rug.nl> Co-authored-by: Luca D. Fehlings <l.d.fehlings@rug.nl>
This commit is contained in:
commit
9fabbdefc0
75 files changed
+447515
No files matched your search
File diff suppressed because it is too large.
Load diff
Binary file not shown.
@@ -0,0 +1,247 @@
|
||||
import marimo
|
||||
|
||||
__generated_with = "0.19.4"
|
||||
app = marimo.App(width="medium")
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
import marimo as mo
|
||||
from wigglystuff import Slider2D
|
||||
|
||||
return Slider2D, mo
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
import diffrax
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
return diffrax, jax, jnp, np, plt
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(diffrax, jax, jnp):
|
||||
def vector_field(t, state, args):
|
||||
u, v = state
|
||||
alpha, beta, gamma, kappa, sigma, delta = args
|
||||
|
||||
z = jax.nn.tanh(kappa * (v - u))
|
||||
|
||||
# Prey dynamics
|
||||
du = (1 - alpha * jnp.exp(beta * v) * (1 - gamma * (0.3 - u))) + sigma * z
|
||||
|
||||
# Predator dynamics
|
||||
dv = (-1 + alpha * jnp.exp(beta * u) * (1 + gamma * (0.3 - v))) + sigma * z
|
||||
|
||||
return jnp.array([du, dv])
|
||||
|
||||
def compute_nullclines(vector_field, u_range, v_range, args, resolution=200):
|
||||
"""
|
||||
Compute nullclines
|
||||
du/dt = 0 (u-nullcline)
|
||||
dv/dt = 0 (v-nullcline)
|
||||
"""
|
||||
alpha, beta, gamma, kappa, sigma, delta = args
|
||||
u_vals = jnp.linspace(u_range[0], u_range[1], resolution)
|
||||
v_vals = jnp.linspace(v_range[0], v_range[1], resolution)
|
||||
U, V = jnp.meshgrid(u_vals, v_vals)
|
||||
|
||||
dU, dV = vector_field(0, [U, V], args)
|
||||
|
||||
return U, V, dU, dV
|
||||
|
||||
def solve(dyn, y0, p, T, n=500):
|
||||
sol = diffrax.diffeqsolve(
|
||||
diffrax.ODETerm(dyn),
|
||||
diffrax.Tsit5(),
|
||||
t0=0.0,
|
||||
t1=T,
|
||||
dt0=0.0001,
|
||||
y0=y0,
|
||||
args=p,
|
||||
saveat=diffrax.SaveAt(ts=jnp.linspace(0, T, n)),
|
||||
stepsize_controller=diffrax.PIDController(rtol=1e-7, atol=1e-8),
|
||||
max_steps=50000,
|
||||
)
|
||||
return sol.ts, sol.ys
|
||||
|
||||
return compute_nullclines, solve, vector_field
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(Slider2D, mo):
|
||||
# alpha = 0.5 # I_n0 / I_bias ratio
|
||||
# beta = 0.39/0.025 # k / U_t (inverse thermal scale)
|
||||
# gamma = 0.26 # coupling coefficient
|
||||
# kappa = 5.0 # tanh steepness
|
||||
# sigma = 0.6 # bias scaling (s * I_bias normalized)
|
||||
# y0 = jnp.array([0.2, 0.4])
|
||||
# ts, ys = solve(vector_field, y0, params, 140)
|
||||
|
||||
alpha = mo.ui.slider(
|
||||
0.0004, 0.012, 0.00001, 0.00129, label="alpha", orientation="vertical"
|
||||
)
|
||||
beta = mo.ui.slider(
|
||||
0.0, 30, 0.00001, 0.39 / 0.025, label="beta", orientation="vertical"
|
||||
)
|
||||
gamma = mo.ui.slider(0, 1, 0.01, 0.26, label="gamma", orientation="vertical")
|
||||
kappa = mo.ui.slider(0, 30, 1.0, 10.0, label="kappa", orientation="vertical")
|
||||
sigma = mo.ui.slider(0, 1, 0.01, 0.6, label="sigma", orientation="vertical")
|
||||
delta = mo.ui.slider(1, 100.0, 1, 10, label="delta", orientation="vertical")
|
||||
|
||||
# v0 = mo.ui.slider(0, 1.0, 0.01, 0.3, label="v0")
|
||||
# u0 = mo.ui.slider(0, 1.0, 0.01, 0.2, label="u0", orientation="vertical")
|
||||
|
||||
state0 = mo.ui.anywidget(
|
||||
Slider2D(
|
||||
x=0.34,
|
||||
y=0.38,
|
||||
width=150,
|
||||
height=150,
|
||||
x_bounds=(0.0, 0.6),
|
||||
y_bounds=(0.0, 0.6),
|
||||
)
|
||||
)
|
||||
|
||||
mo.hstack(
|
||||
[
|
||||
mo.plain_text("""
|
||||
alpha: I_n0 / I_bias ratio
|
||||
beta: k / U_t ratio
|
||||
gamma: coupling coefficient
|
||||
kappa: tanh steepness
|
||||
sigma: bias scaling (s * I_bias)
|
||||
"""),
|
||||
mo.hstack(
|
||||
[state0, alpha, beta, gamma, kappa, sigma, delta], justify="start"
|
||||
),
|
||||
]
|
||||
)
|
||||
return alpha, beta, delta, gamma, kappa, sigma, state0
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(
|
||||
alpha,
|
||||
beta,
|
||||
compute_nullclines,
|
||||
delta,
|
||||
gamma,
|
||||
jnp,
|
||||
kappa,
|
||||
np,
|
||||
plt,
|
||||
sigma,
|
||||
solve,
|
||||
state0,
|
||||
vector_field,
|
||||
):
|
||||
params = (
|
||||
alpha.value,
|
||||
beta.value,
|
||||
gamma.value,
|
||||
kappa.value,
|
||||
sigma.value,
|
||||
delta.value,
|
||||
)
|
||||
ic_neuro = [state0.x, state0.y]
|
||||
|
||||
u_range = [0.0, 0.6]
|
||||
v_range = [0.0, 0.6]
|
||||
|
||||
u_sparse = jnp.linspace(u_range[0], u_range[1], 20)
|
||||
v_sparse = jnp.linspace(v_range[0], v_range[1], 20)
|
||||
|
||||
Us, Vs = jnp.meshgrid(u_sparse, v_sparse)
|
||||
|
||||
def plot_vf(ax, vector_field):
|
||||
U, V, dU, dV = compute_nullclines(vector_field, u_range, v_range, params)
|
||||
dUs, dVs = vector_field(0, [Us, Vs], params)
|
||||
|
||||
# Normalize for visualization
|
||||
magnitude = np.sqrt(dUs**2 + dVs**2)
|
||||
magnitude[magnitude == 0] = 1
|
||||
dUs_norm = dUs / magnitude
|
||||
dVs_norm = dVs / magnitude
|
||||
|
||||
# Nullclines
|
||||
ax.contour(U, V, dU, levels=[0], colors="blue", linewidths=2, linestyles="-")
|
||||
ax.contour(U, V, dV, levels=[0], colors="red", linewidths=2, linestyles="-")
|
||||
|
||||
ax.quiver(Us, Vs, dUs_norm, dVs_norm, magnitude, cmap="viridis", alpha=0.6)
|
||||
|
||||
# Trajectories
|
||||
color = plt.cm.plasma(0.2)
|
||||
ts, ys = solve(vector_field, jnp.array(ic_neuro), params, delta.value)
|
||||
ax.plot(ys[:, 0], ys[:, 1], "-", color=color, linewidth=1.5, alpha=0.8)
|
||||
ax.plot(ic_neuro[0], ic_neuro[1], "o", color=color, markersize=6)
|
||||
|
||||
ax.set_xlabel("u (Prey)")
|
||||
ax.set_ylabel("v (Predator)")
|
||||
ax.set_title("Wererabbit: Phase Portrait")
|
||||
ax.legend(["u-nullcline (du/dt=0)", "v-nullcline (dv/dt=0)"], loc="upper right")
|
||||
ax.set_xlim(u_range)
|
||||
ax.set_ylim(v_range)
|
||||
ax.axhline(y=0, color="gray", linestyle="--", alpha=0.3)
|
||||
ax.axvline(x=0, color="gray", linestyle="--", alpha=0.3)
|
||||
|
||||
def plot_trj(ax, vector_field):
|
||||
ts, ys = solve(vector_field, jnp.array(ic_neuro), params, delta.value)
|
||||
ax.plot(ts, ys[:, 0], "b-", linewidth=2, label="u (Prey)")
|
||||
ax.plot(ts, ys[:, 1], "r-", linewidth=2, label="v (Predator)")
|
||||
|
||||
ax.set_xlabel("Time τ")
|
||||
ax.set_ylabel("Population")
|
||||
ax.set_title(
|
||||
f"Wererabbit: Time Series (IC: u₀={ic_neuro[0]:.2f}, v₀={ic_neuro[1]:.2f})"
|
||||
)
|
||||
ax.legend()
|
||||
ax.axhline(y=0, color="gray", linestyle="--", alpha=0.3)
|
||||
|
||||
fig = plt.figure(figsize=(10, 4))
|
||||
|
||||
# --- Plot 1: Wererabbit Phase Portrait ---
|
||||
ax1 = fig.add_subplot(1, 2, 1)
|
||||
plot_vf(ax1, vector_field)
|
||||
|
||||
ax2 = fig.add_subplot(1, 2, 2)
|
||||
plot_trj(ax2, vector_field)
|
||||
|
||||
# ax3 = fig.add_subplot(3, 2, 3)
|
||||
# plot_vf(ax3, vector_field_prod)
|
||||
|
||||
# ax4 = fig.add_subplot(3, 2, 4)
|
||||
# plot_trj(ax4, vector_field_prod)
|
||||
|
||||
# ax5 = fig.add_subplot(3, 2, 5)
|
||||
# plot_vf(ax5, vector_field_exp)
|
||||
|
||||
# ax6 = fig.add_subplot(3, 2, 6)
|
||||
# plot_trj(ax6, vector_field_exp)
|
||||
|
||||
plt.tight_layout()
|
||||
fig
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "b433e05f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import diffrax as dfx\n",
|
||||
"import jax\n",
|
||||
"import jax.numpy as jnp\n",
|
||||
"import jax.random as jrand\n",
|
||||
"import matplotlib as mpl\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"\n",
|
||||
"from felice.neuron_models import FHNRS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"id": "6abc4178",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"key = jrand.key(0)\n",
|
||||
"max_time = 200\n",
|
||||
"\n",
|
||||
"neuron_model = FHNRS(\n",
|
||||
" gmax_pasive=2.0,\n",
|
||||
" Erev_pasive=0.0,\n",
|
||||
" a_fast=-2.0,\n",
|
||||
" voff_fast=0.0,\n",
|
||||
" tau_fast=0.0,\n",
|
||||
" a_slow=0.5,\n",
|
||||
" voff_slow=1.0,\n",
|
||||
" tau_slow=50.0,\n",
|
||||
" vthr=jnp.inf,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def state_at_t(comp_times):\n",
|
||||
" sol = dfx.diffeqsolve(\n",
|
||||
" terms=dfx.ODETerm(neuron_model.dynamics),\n",
|
||||
" solver=dfx.Tsit5(),\n",
|
||||
" t0=0.0,\n",
|
||||
" t1=max_time,\n",
|
||||
" dt0=1e-3,\n",
|
||||
" y0=neuron_model.init_state(1)\n",
|
||||
" + jrand.uniform(key, shape=(1, 3), minval=0.1, maxval=0.5),\n",
|
||||
" saveat=dfx.SaveAt(ts=comp_times),\n",
|
||||
" max_steps=200000,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" return sol.ts, sol.ys"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"id": "9d185d60",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1380x460 with 3 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"v_range = jnp.arange(-3.1, 3, 0.1)\n",
|
||||
"VI_inst = jax.vmap(neuron_model.IV_inst)(v_range)\n",
|
||||
"VI_fast = jax.vmap(neuron_model.IV_fast)(v_range)\n",
|
||||
"VI_slow = jax.vmap(neuron_model.IV_slow)(v_range)\n",
|
||||
"\n",
|
||||
"with mpl.style.context(\"boilerplot.ieeetran\"):\n",
|
||||
" fig, ax = plt.subplots(1, 3, figsize=(6.9, 2.3), dpi=200.0, sharey=True)\n",
|
||||
" ax[0].plot(v_range, VI_inst)\n",
|
||||
" ax[1].plot(v_range, VI_fast)\n",
|
||||
" ax[2].plot(v_range, VI_slow)\n",
|
||||
" plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"id": "d98ace6e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"comp_times = jnp.linspace(0.0, max_time, 500)\n",
|
||||
"_, state = state_at_t(comp_times)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"id": "06ae6a94",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def compute_nullclines(neuron_model, u_range, v_range, resolution=200):\n",
|
||||
" \"\"\"\n",
|
||||
" Compute nullclines\n",
|
||||
" du/dt = 0 (u-nullcline)\n",
|
||||
" dv/dt = 0 (v-nullcline)\n",
|
||||
" \"\"\"\n",
|
||||
" u_vals = jnp.linspace(u_range[0], u_range[1], resolution)\n",
|
||||
" v_vals = jnp.linspace(v_range[0], v_range[1], resolution)\n",
|
||||
" U, V = jnp.meshgrid(u_vals, v_vals)\n",
|
||||
"\n",
|
||||
" UV = jnp.stack(\n",
|
||||
" [U.reshape(-1), V.reshape(-1), jnp.zeros((resolution * resolution,))], axis=1\n",
|
||||
" )\n",
|
||||
" dS = neuron_model.dynamics(0, UV, {})\n",
|
||||
" dU = dS[:, 0].reshape(U.shape)\n",
|
||||
" dV = dS[:, 1].reshape(V.shape)\n",
|
||||
"\n",
|
||||
" return U, V, dU, dV\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def plot_vf(ax, neuron_model, u_range, v_range):\n",
|
||||
" import numpy as np\n",
|
||||
"\n",
|
||||
" u_sparse = jnp.linspace(u_range[0], u_range[1], 30)\n",
|
||||
" v_sparse = jnp.linspace(v_range[0], v_range[1], 30)\n",
|
||||
"\n",
|
||||
" Us, Vs = jnp.meshgrid(u_sparse, v_sparse)\n",
|
||||
"\n",
|
||||
" U, V, dU, dV = compute_nullclines(neuron_model, u_range, v_range, 200)\n",
|
||||
"\n",
|
||||
" UVs = jnp.stack([Us.reshape(-1), Vs.reshape(-1), jnp.ones((30 * 30,))], axis=1)\n",
|
||||
" dS = neuron_model.dynamics(0, UVs, {})\n",
|
||||
" dUs = dS[:, 0].reshape(Us.shape)\n",
|
||||
" dVs = dS[:, 1].reshape(Vs.shape)\n",
|
||||
"\n",
|
||||
" # Normalize for visualization\n",
|
||||
" magnitude = np.sqrt(dUs**2 + dVs**2)\n",
|
||||
" magnitude[magnitude == 0] = 1\n",
|
||||
" dUs_norm = dUs / magnitude\n",
|
||||
" dVs_norm = dVs / magnitude\n",
|
||||
"\n",
|
||||
" # Nullclines\n",
|
||||
" ax.contour(U, V, dU, levels=[0], colors=\"blue\", linewidths=1, linestyles=\"-\")\n",
|
||||
" ax.contour(U, V, dV, levels=[0], colors=\"red\", linewidths=1, linestyles=\"-\")\n",
|
||||
"\n",
|
||||
" # Vector field\n",
|
||||
" ax.quiver(Us, Vs, dUs_norm, dVs_norm, magnitude, cmap=\"viridis\", alpha=0.6)\n",
|
||||
"\n",
|
||||
" ax.set_xlabel(\"v\")\n",
|
||||
" ax.set_ylabel(\"w\")\n",
|
||||
" ax.legend([\"u-nullcline (du/dt=0)\", \"v-nullcline (dv/dt=0)\"], loc=\"upper right\")\n",
|
||||
" ax.set_xlim(u_range)\n",
|
||||
" ax.set_ylim(v_range)\n",
|
||||
" ax.axhline(y=0, color=\"gray\", linestyle=\"--\", alpha=0.3)\n",
|
||||
" ax.axvline(x=0, color=\"gray\", linestyle=\"--\", alpha=0.3)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 27,
|
||||
"id": "b73fb06d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1380x520 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"with mpl.style.context(\"boilerplot.ieeetran\"):\n",
|
||||
" fig, ax = plt.subplots(1, 2, figsize=(6.9, 2.6), dpi=200)\n",
|
||||
" ax[0].plot(comp_times, state[:, 0, 0])\n",
|
||||
" ax[0].plot(comp_times, state[:, 0, 1], \"--\")\n",
|
||||
" # ax[0].plot(comp_times, state[0, :, 2], \"-.\")\n",
|
||||
" ax[0].set_xlabel(\"Time (ms)\")\n",
|
||||
" ax[0].legend([\"v\", \"vslow\", \"syn\"])\n",
|
||||
"\n",
|
||||
" plot_vf(ax[1], neuron_model, [-2, 2], [-2, 2])\n",
|
||||
"\n",
|
||||
" ax[1].plot(state[:, 0, 0], state[:, 0, 1])\n",
|
||||
" ax[1].plot(state[0, 0, 0], state[0, 0, 1], \".\", label=\"start\")\n",
|
||||
" ax[1].plot(state[0, -1, 0], state[0, -1, 1], \".\", label=\"end\")\n",
|
||||
" ax[1].set_xlabel(\"v\")\n",
|
||||
" ax[1].set_ylabel(\"v fast\")\n",
|
||||
" ax[1].legend()\n",
|
||||
" plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5a6f0b9f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "daf213b5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "felice",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import marimo
|
||||
|
||||
__generated_with = "0.19.4"
|
||||
app = marimo.App(width="medium")
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
import diffrax as dfx
|
||||
import jax.numpy as jnp
|
||||
import marimo as mo
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from jax import jit
|
||||
|
||||
return dfx, jit, jnp, mo, np, plt
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(dfx, jnp):
|
||||
def vector_field(t, y, args):
|
||||
v, vslow = y
|
||||
|
||||
ipasive = args["gmax"] * (v - args["Erev"])
|
||||
ifast = args["af"] * jnp.tanh(v - args["Ef"])
|
||||
islow = args["as"] * jnp.tanh(vslow - args["Es"])
|
||||
|
||||
dv = (-ipasive - ifast - islow) / args["C"]
|
||||
dvs = (v - vslow) / args["ts"]
|
||||
|
||||
return jnp.array([dv, dvs])
|
||||
|
||||
term = dfx.ODETerm(vector_field)
|
||||
return term, vector_field
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
p1 = mo.ui.slider(0.0, 5.0, value=1.0, step=0.1, label="gmax")
|
||||
p2 = mo.ui.slider(-1.0, 1.0, value=0.0, step=0.1, label="Erev")
|
||||
p3 = mo.ui.slider(-5.0, 5.0, value=-2.0, step=0.05, label="af")
|
||||
p4 = mo.ui.slider(-1.0, 1.0, value=0.0, step=0.05, label="Ef")
|
||||
p5 = mo.ui.slider(-5.0, 5.0, value=2.0, step=0.05, label="as")
|
||||
p6 = mo.ui.slider(-1.0, 1.0, value=0.0, step=0.05, label="Es")
|
||||
p7 = mo.ui.slider(1.0, 100.0, value=50.0, step=0.1, label="ts")
|
||||
p8 = mo.ui.slider(0.0, 1.0, value=1.0, step=0.01, label="C")
|
||||
|
||||
mo.hstack(
|
||||
[
|
||||
mo.vstack([p1, p2, p3, p4], justify="start", gap=1),
|
||||
mo.vstack([p5, p6, p7, p8], justify="start", gap=1),
|
||||
]
|
||||
)
|
||||
return p1, p2, p3, p4, p5, p6, p7, p8
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
mo.md("""
|
||||
### Initial Conditions & Simulation
|
||||
""")
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
x0 = mo.ui.slider(-5.0, 5.0, value=2.0, step=0.1, label="x₀")
|
||||
y0 = mo.ui.slider(-5.0, 5.0, value=0.0, step=0.1, label="y₀")
|
||||
t_max = mo.ui.slider(10, 100, value=30, step=5, label="t_max")
|
||||
|
||||
mo.hstack([x0, y0, t_max], justify="start", gap=2)
|
||||
return t_max, x0, y0
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(dfx, jit, jnp, p1, p2, p3, p4, p5, p6, p7, p8, t_max, term, x0, y0):
|
||||
@jit
|
||||
def solve_ode(y_init, args, t_end):
|
||||
solver = dfx.Tsit5()
|
||||
saveat = dfx.SaveAt(ts=jnp.linspace(0, t_end, 2000))
|
||||
sol = dfx.diffeqsolve(
|
||||
term,
|
||||
solver,
|
||||
t0=0,
|
||||
t1=t_end,
|
||||
dt0=0.01,
|
||||
y0=y_init,
|
||||
args=args,
|
||||
saveat=saveat,
|
||||
max_steps=100000,
|
||||
)
|
||||
return sol.ts, sol.ys
|
||||
|
||||
args = {
|
||||
"gmax": p1.value,
|
||||
"Erev": p2.value,
|
||||
"af": p3.value,
|
||||
"Ef": p4.value,
|
||||
"as": p5.value,
|
||||
"Es": p6.value,
|
||||
"ts": p7.value,
|
||||
"C": p8.value,
|
||||
}
|
||||
y_init = jnp.array([x0.value, y0.value])
|
||||
|
||||
t, ys = solve_ode(y_init, args, float(t_max.value))
|
||||
x_sol = ys[:, 0]
|
||||
y_sol = ys[:, 1]
|
||||
return args, t, x_sol, y_sol
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(args, jnp, np, plt, t, vector_field, x0, x_sol, y0, y_sol):
|
||||
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
|
||||
|
||||
# Time series
|
||||
|
||||
ax1.plot(t, x_sol, "b-", lw=1.5, label="x(t)")
|
||||
ax1.plot(t, y_sol, "r-", lw=1.5, label="y(t)")
|
||||
ax1.set_xlabel("Time t")
|
||||
ax1.set_ylabel("State")
|
||||
ax1.set_title("Transient Response")
|
||||
ax1.legend()
|
||||
ax1.grid(True, alpha=0.3)
|
||||
|
||||
# Phase plane bounds
|
||||
# pad = 1.0
|
||||
xmin, xmax = -4, 4
|
||||
ymin, ymax = -2.5, 2.5
|
||||
|
||||
# Vector field
|
||||
X, Y = jnp.meshgrid(jnp.linspace(xmin, xmax, 20), jnp.linspace(ymin, ymax, 20))
|
||||
U, V = jnp.zeros_like(X), np.zeros_like(Y)
|
||||
|
||||
state = jnp.stack([X, Y], axis=0)
|
||||
deriv = vector_field(0.0, state, args)
|
||||
dx, dy = deriv[0], deriv[1]
|
||||
mag = jnp.sqrt(dx**2 + dy**2)
|
||||
U = jnp.where(mag > 0, dx / mag, U)
|
||||
V = jnp.where(mag > 0, dy / mag, V)
|
||||
|
||||
ax2.quiver(X, Y, U, V, alpha=0.4, color="gray", scale=25)
|
||||
|
||||
# Nullclines
|
||||
Xf, Yf = jnp.meshgrid(jnp.linspace(xmin, xmax, 150), jnp.linspace(ymin, ymax, 150))
|
||||
DX, DY = jnp.zeros_like(Xf), jnp.zeros_like(Yf)
|
||||
state = jnp.stack([Xf, Yf], axis=0)
|
||||
deriv = vector_field(0.0, state, args)
|
||||
DX, DY = deriv[0], deriv[1]
|
||||
ax2.contour(
|
||||
Xf,
|
||||
Yf,
|
||||
DX,
|
||||
levels=[0],
|
||||
colors="blue",
|
||||
linestyles="--",
|
||||
linewidths=1.5,
|
||||
alpha=0.7,
|
||||
)
|
||||
ax2.contour(
|
||||
Xf, Yf, DY, levels=[0], colors="red", linestyles="--", linewidths=1.5, alpha=0.7
|
||||
)
|
||||
|
||||
# Trajectory
|
||||
ax2.plot(x_sol, y_sol, "b-", lw=2)
|
||||
ax2.plot(x0.value, y0.value, "go", ms=10, label="Start")
|
||||
ax2.plot(x_sol[-1], y_sol[-1], "r*", ms=12, label="End")
|
||||
|
||||
ax2.set_xlabel("x")
|
||||
ax2.set_ylabel("y")
|
||||
ax2.set_title("Phase Plane")
|
||||
ax2.legend()
|
||||
ax2.grid(True, alpha=0.3)
|
||||
ax2.set_xlim(xmin, xmax)
|
||||
ax2.set_ylim(ymin, ymax)
|
||||
|
||||
plt.tight_layout()
|
||||
fig
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
@@ -0,0 +1,847 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "593c2428",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import diffrax\n",
|
||||
"import jax\n",
|
||||
"import jax.numpy as jnp\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"plt.style.use(\"seaborn-v0_8-whitegrid\")\n",
|
||||
"plt.rcParams[\"figure.figsize\"] = [14, 10]\n",
|
||||
"plt.rcParams[\"font.size\"] = 11\n",
|
||||
"plt.rcParams[\"axes.labelsize\"] = 12\n",
|
||||
"plt.rcParams[\"axes.titlesize\"] = 13"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ebbc1603",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## System definition"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "075bfd5d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"2.58e-13"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"129e-15 / 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "f40d4a9b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Neuromorphic system parameters (dimensionless)\n",
|
||||
"alpha = 0.5 # I_n0 / I_bias ratio\n",
|
||||
"beta = 1.5 # k / U_t (inverse thermal scale)\n",
|
||||
"gamma = 0.26 # coupling coefficient\n",
|
||||
"kappa = 5.0 # tanh steepness\n",
|
||||
"sigma = 0.6 # bias scaling (s * I_bias normalized)\n",
|
||||
"\n",
|
||||
"# Classic Lotka-Volterra parameters\n",
|
||||
"a = 1.0 # prey growth rate\n",
|
||||
"b = 0.5 # predation rate\n",
|
||||
"c = 0.5 # predator death rate\n",
|
||||
"d = 0.5 # predator growth from prey\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def z_func(u, v):\n",
|
||||
" \"\"\"Coupling function\"\"\"\n",
|
||||
" return jnp.tanh(kappa * (u - v))\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def neuromorphic_system(t, state, args):\n",
|
||||
" \"\"\"\n",
|
||||
" Neuromorphic oscillator interpreted as predator-prey\n",
|
||||
" u = prey, v = predator\n",
|
||||
" \"\"\"\n",
|
||||
" u, v = state\n",
|
||||
" z = z_func(u, v)\n",
|
||||
"\n",
|
||||
" # Prey dynamics\n",
|
||||
" du = z * (1 - alpha * jnp.exp(beta * v) * (1 + gamma * (0.5 - u))) - sigma\n",
|
||||
"\n",
|
||||
" # Predator dynamics\n",
|
||||
" dv = z * (-1 + alpha * jnp.exp(beta * u) * (1 + gamma * (0.5 - v))) - sigma\n",
|
||||
"\n",
|
||||
" return jnp.array([du, dv])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def lotka_volterra(t, state, args):\n",
|
||||
" \"\"\"Classic predator-prey system\"\"\"\n",
|
||||
" x, y = state\n",
|
||||
" dx = x * (a - b * y)\n",
|
||||
" dy = y * (-c + d * x)\n",
|
||||
" return jnp.array([dx, dy])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def generalized_predator_prey(state, t):\n",
|
||||
" \"\"\"\n",
|
||||
" Modified Lotka-Volterra with saturation (for fairer comparison)\n",
|
||||
" \"\"\"\n",
|
||||
" x, y = state\n",
|
||||
" K = 3.0 # carrying capacity\n",
|
||||
" dx = x * (a * (1 - x / K) - b * y)\n",
|
||||
" dy = y * (-c + d * x)\n",
|
||||
" return jnp.array([dx, dy])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def solve(dyn, y0, p, T, n=1000):\n",
|
||||
" sol = diffrax.diffeqsolve(\n",
|
||||
" diffrax.ODETerm(dyn),\n",
|
||||
" diffrax.Tsit5(),\n",
|
||||
" t0=0.0,\n",
|
||||
" t1=T,\n",
|
||||
" dt0=0.01,\n",
|
||||
" y0=y0,\n",
|
||||
" args=p,\n",
|
||||
" saveat=diffrax.SaveAt(ts=jnp.linspace(0, T, n)),\n",
|
||||
" stepsize_controller=diffrax.PIDController(rtol=1e-6, atol=1e-8),\n",
|
||||
" max_steps=50000,\n",
|
||||
" )\n",
|
||||
" return sol.ts, sol.ys"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "c7d5821a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def compute_nullclines_neuromorphic(u_range, v_range, resolution=200):\n",
|
||||
" \"\"\"\n",
|
||||
" Compute nullclines for neuromorphic system\n",
|
||||
" du/dt = 0 (u-nullcline)\n",
|
||||
" dv/dt = 0 (v-nullcline)\n",
|
||||
" \"\"\"\n",
|
||||
" u_vals = jnp.linspace(u_range[0], u_range[1], resolution)\n",
|
||||
" v_vals = jnp.linspace(v_range[0], v_range[1], resolution)\n",
|
||||
" U, V = jnp.meshgrid(u_vals, v_vals)\n",
|
||||
"\n",
|
||||
" Z = z_func(U, V)\n",
|
||||
"\n",
|
||||
" # du/dt\n",
|
||||
" dU = Z * (1 - alpha * jnp.exp(beta * V) * (1 + gamma * (0.5 - U))) - sigma\n",
|
||||
"\n",
|
||||
" # dv/dt\n",
|
||||
" dV = Z * (-1 + alpha * jnp.exp(beta * U) * (1 + gamma * (0.5 - V))) - sigma\n",
|
||||
"\n",
|
||||
" return U, V, dU, dV\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def compute_nullclines_lotka_volterra(x_range, y_range, resolution=200):\n",
|
||||
" \"\"\"Compute nullclines for classic Lotka-Volterra\"\"\"\n",
|
||||
" x_vals = jnp.linspace(x_range[0], x_range[1], resolution)\n",
|
||||
" y_vals = jnp.linspace(y_range[0], y_range[1], resolution)\n",
|
||||
" X, Y = jnp.meshgrid(x_vals, y_vals)\n",
|
||||
"\n",
|
||||
" # dx/dt = x(a - by)\n",
|
||||
" dX = X * (a - b * Y)\n",
|
||||
"\n",
|
||||
" # dy/dt = y(-c + dx)\n",
|
||||
" dY = Y * (-c + d * X)\n",
|
||||
"\n",
|
||||
" return X, Y, dX, dY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fd15b70a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Fixpoint analysis"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f5a7f679",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[[0.0, 0.0], [1.0, 2.0]]"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def newton_fp(f, x0, p, tol=1e-12, iters=50):\n",
|
||||
" x = jnp.array(x0)\n",
|
||||
" for _ in range(iters):\n",
|
||||
" fx = f(0.0, x, p)\n",
|
||||
" J = jax.jacrev(lambda y: f(0.0, y, p))(x)\n",
|
||||
" x = x - jnp.linalg.solve(J, fx)\n",
|
||||
" if jnp.linalg.norm(fx) < tol:\n",
|
||||
" return x, True\n",
|
||||
" return x, False\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def find_fixed_points(fn):\n",
|
||||
" \"\"\"Find fixed points numerically\"\"\"\n",
|
||||
" fixed_points = []\n",
|
||||
"\n",
|
||||
" # Try multiple initial guesses\n",
|
||||
" initial_guesses = [\n",
|
||||
" [0.0, 0.0],\n",
|
||||
" [0.5, 0.5],\n",
|
||||
" [-0.5, -0.5],\n",
|
||||
" [1.0, 0.0],\n",
|
||||
" [0.0, 1.0],\n",
|
||||
" [-1.0, 0.0],\n",
|
||||
" [0.5, -0.5],\n",
|
||||
" [-0.5, 0.5],\n",
|
||||
" [0.3, 0.3],\n",
|
||||
" [2.0, 0.0],\n",
|
||||
" [0.0, 2.0],\n",
|
||||
" [-2.0, 0.0],\n",
|
||||
" [1.5, -1.5],\n",
|
||||
" [-1.5, 1.5],\n",
|
||||
" [1.3, 1.3],\n",
|
||||
" [2.0, -1.5],\n",
|
||||
" [-2.5, 1.5],\n",
|
||||
" [2, 1.5],\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
" for guess in initial_guesses:\n",
|
||||
" point, _ = newton_fp(fn, guess, None)\n",
|
||||
"\n",
|
||||
" # Check if it's actually a fixed point\n",
|
||||
" residual = jnp.linalg.norm(fn(0, point, None))\n",
|
||||
" if residual < 1e-6:\n",
|
||||
" # Check if we already have this point\n",
|
||||
" is_new = True\n",
|
||||
" for fp in fixed_points:\n",
|
||||
" if jnp.linalg.norm(jnp.array(fp) - jnp.array(point)) < 0.01:\n",
|
||||
" is_new = False\n",
|
||||
" break\n",
|
||||
" if is_new:\n",
|
||||
" fixed_points.append(point.tolist())\n",
|
||||
"\n",
|
||||
" return fixed_points\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# find_fixed_points(neuromorphic_system)\n",
|
||||
"find_fixed_points(lotka_volterra)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "daaa31a8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Stability"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "ec312bf8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def calc_jacobian(u, v, fn, eps=1e-6):\n",
|
||||
" \"\"\"Numerical Jacobian for neuromorphic system\"\"\"\n",
|
||||
" jac = jax.jacobian(fn, argnums=1)(0, [u, v], None)\n",
|
||||
"\n",
|
||||
" J = jnp.zeros((2, 2))\n",
|
||||
" # Partial derivatives\n",
|
||||
" J = J.at[0, 0].set(jac[0][0])\n",
|
||||
" J = J.at[0, 1].set(jac[0][1])\n",
|
||||
" J = J.at[1, 0].set(jac[1][0])\n",
|
||||
" J = J.at[1, 1].set(jac[1][1])\n",
|
||||
"\n",
|
||||
" return J\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def analyze_stability(J):\n",
|
||||
" \"\"\"Analyze stability from Jacobian eigenvalues\"\"\"\n",
|
||||
" eigenvalues = jnp.linalg.eigvals(J)\n",
|
||||
"\n",
|
||||
" real_parts = jnp.real(eigenvalues)\n",
|
||||
" imag_parts = jnp.imag(eigenvalues)\n",
|
||||
"\n",
|
||||
" # Classification\n",
|
||||
" if all(r < 0 for r in real_parts):\n",
|
||||
" if any(abs(i) > 1e-10 for i in imag_parts):\n",
|
||||
" stability = \"Stable Focus (spiral sink)\"\n",
|
||||
" else:\n",
|
||||
" stability = \"Stable Node\"\n",
|
||||
" elif all(r > 0 for r in real_parts):\n",
|
||||
" if any(abs(i) > 1e-10 for i in imag_parts):\n",
|
||||
" stability = \"Unstable Focus (spiral source)\"\n",
|
||||
" else:\n",
|
||||
" stability = \"Unstable Node\"\n",
|
||||
" elif real_parts[0] * real_parts[1] < 0:\n",
|
||||
" stability = \"Saddle Point\"\n",
|
||||
" else:\n",
|
||||
" if any(abs(i) > 1e-10 for i in imag_parts):\n",
|
||||
" stability = \"Center (neutral)\"\n",
|
||||
" else:\n",
|
||||
" stability = \"Non-hyperbolic\"\n",
|
||||
"\n",
|
||||
" return eigenvalues, stability"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "37996ccd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Main"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "640c686a",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"======================================================================\n",
|
||||
"PREDATOR-PREY ANALYSIS: NEUROMORPHIC vs LOTKA-VOLTERRA\n",
|
||||
"======================================================================\n",
|
||||
"\n",
|
||||
"======================================================================\n",
|
||||
"1. FIXED POINT ANALYSIS\n",
|
||||
"======================================================================\n",
|
||||
"\n",
|
||||
"--- Neuromorphic System ---\n",
|
||||
"Found 2 fixed point(s):\n",
|
||||
" FP1: (u*, v*) = (0.6762, -0.1182)\n",
|
||||
" Eigenvalues: λ₁ = -0.1247+1.1803j, λ₂ = -0.1247-1.1803j\n",
|
||||
" Classification: Stable Focus (spiral sink)\n",
|
||||
" FP2: (u*, v*) = (-0.1182, 0.6762)\n",
|
||||
" Eigenvalues: λ₁ = -0.1247+1.1803j, λ₂ = -0.1247-1.1803j\n",
|
||||
" Classification: Stable Focus (spiral sink)\n",
|
||||
"\n",
|
||||
"--- Lotka-Volterra System ---\n",
|
||||
"Fixed points:\n",
|
||||
" FP1: (x*, y*) = (0.0000, 0.0000)\n",
|
||||
" (Origin - typically a saddle)\n",
|
||||
" FP2: (x*, y*) = (1.0000, 2.0000)\n",
|
||||
" Eigenvalues: λ₁ = 0.0000+0.7071j, λ₂ = 0.0000-0.7071j\n",
|
||||
" Classification: Center (neutral)\n",
|
||||
"\n",
|
||||
"======================================================================\n",
|
||||
"2. NULLCLINE EQUATIONS\n",
|
||||
"======================================================================\n",
|
||||
"\n",
|
||||
"--- Neuromorphic System ---\n",
|
||||
"u-nullcline (du/dt = 0):\n",
|
||||
" z·[1 - α·exp(βv)·(1 + γ(½-u))] = σ\n",
|
||||
" where z = tanh(κ(u-v))\n",
|
||||
"\n",
|
||||
"v-nullcline (dv/dt = 0):\n",
|
||||
" z·[-1 + α·exp(βu)·(1 + γ(½-v))] = σ\n",
|
||||
"\n",
|
||||
"--- Lotka-Volterra System ---\n",
|
||||
"x-nullcline (dx/dt = 0):\n",
|
||||
" x = 0 OR y = a/b\n",
|
||||
"\n",
|
||||
"y-nullcline (dy/dt = 0):\n",
|
||||
" y = 0 OR x = c/d\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(\"=\" * 70)\n",
|
||||
"print(\"PREDATOR-PREY ANALYSIS: NEUROMORPHIC vs LOTKA-VOLTERRA\")\n",
|
||||
"print(\"=\" * 70)\n",
|
||||
"\n",
|
||||
"# --- Fixed Points ---\n",
|
||||
"print(\"\\n\" + \"=\" * 70)\n",
|
||||
"print(\"1. FIXED POINT ANALYSIS\")\n",
|
||||
"print(\"=\" * 70)\n",
|
||||
"\n",
|
||||
"print(\"\\n--- Neuromorphic System ---\")\n",
|
||||
"fp_neuro = find_fixed_points(neuromorphic_system)\n",
|
||||
"print(f\"Found {len(fp_neuro)} fixed point(s):\")\n",
|
||||
"for i, fp in enumerate(fp_neuro):\n",
|
||||
" print(f\" FP{i + 1}: (u*, v*) = ({fp[0]:.4f}, {fp[1]:.4f})\")\n",
|
||||
" J = calc_jacobian(fp[0], fp[1], neuromorphic_system)\n",
|
||||
" eigs, stability = analyze_stability(J)\n",
|
||||
" print(f\" Eigenvalues: λ₁ = {eigs[0]:.4f}, λ₂ = {eigs[1]:.4f}\")\n",
|
||||
" print(f\" Classification: {stability}\")\n",
|
||||
"\n",
|
||||
"print(\"\\n--- Lotka-Volterra System ---\")\n",
|
||||
"fp_lv = find_fixed_points(lotka_volterra)\n",
|
||||
"print(\"Fixed points:\")\n",
|
||||
"for i, fp in enumerate(fp_lv):\n",
|
||||
" print(f\" FP{i + 1}: (x*, y*) = ({fp[0]:.4f}, {fp[1]:.4f})\")\n",
|
||||
" if fp[0] > 0 or fp[1] > 0: # avoid division issues at origin\n",
|
||||
" J = calc_jacobian(fp[0], fp[1], lotka_volterra)\n",
|
||||
" eigs, stability = analyze_stability(J)\n",
|
||||
" print(f\" Eigenvalues: λ₁ = {eigs[0]:.4f}, λ₂ = {eigs[1]:.4f}\")\n",
|
||||
" print(f\" Classification: {stability}\")\n",
|
||||
" else:\n",
|
||||
" print(\" (Origin - typically a saddle)\")\n",
|
||||
"\n",
|
||||
"print(\"\\n\" + \"=\" * 70)\n",
|
||||
"print(\"2. NULLCLINE EQUATIONS\")\n",
|
||||
"print(\"=\" * 70)\n",
|
||||
"\n",
|
||||
"print(\"\\n--- Neuromorphic System ---\")\n",
|
||||
"print(\"u-nullcline (du/dt = 0):\")\n",
|
||||
"print(\" z·[1 - α·exp(βv)·(1 + γ(½-u))] = σ\")\n",
|
||||
"print(\" where z = tanh(κ(u-v))\")\n",
|
||||
"print(\"\\nv-nullcline (dv/dt = 0):\")\n",
|
||||
"print(\" z·[-1 + α·exp(βu)·(1 + γ(½-v))] = σ\")\n",
|
||||
"\n",
|
||||
"print(\"\\n--- Lotka-Volterra System ---\")\n",
|
||||
"print(\"x-nullcline (dx/dt = 0):\")\n",
|
||||
"print(\" x = 0 OR y = a/b\")\n",
|
||||
"print(\"\\ny-nullcline (dy/dt = 0):\")\n",
|
||||
"print(\" y = 0 OR x = c/d\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a81cdac9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Visualization"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e1adaf80",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"/tmp/ipykernel_2751701/1982221629.py:146: UserWarning: Glyph 8320 (\\N{SUBSCRIPT ZERO}) missing from font(s) Arial.\n",
|
||||
" plt.tight_layout()\n",
|
||||
"/home/p306945/Projects/felice/.venv/lib/python3.13/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 8320 (\\N{SUBSCRIPT ZERO}) missing from font(s) Arial.\n",
|
||||
" fig.canvas.print_figure(bytes_io, **kw)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1600x1400 with 4 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"======================================================================\n",
|
||||
"3. PHASE PORTRAIT SAVED\n",
|
||||
"======================================================================\n",
|
||||
"Saved: phase_portrait_comparison.png\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fig = plt.figure(figsize=(16, 14))\n",
|
||||
"\n",
|
||||
"# --- Plot 1: Neuromorphic Phase Portrait ---\n",
|
||||
"ax1 = fig.add_subplot(2, 2, 1)\n",
|
||||
"\n",
|
||||
"u_range = [-1.5, 1.5]\n",
|
||||
"v_range = [-1.5, 1.5]\n",
|
||||
"U, V, dU, dV = compute_nullclines_neuromorphic(u_range, v_range)\n",
|
||||
"\n",
|
||||
"# Nullclines\n",
|
||||
"ax1.contour(U, V, dU, levels=[0], colors=\"blue\", linewidths=2, linestyles=\"-\")\n",
|
||||
"ax1.contour(U, V, dV, levels=[0], colors=\"red\", linewidths=2, linestyles=\"-\")\n",
|
||||
"\n",
|
||||
"# Vector field\n",
|
||||
"u_sparse = np.linspace(u_range[0], u_range[1], 20)\n",
|
||||
"v_sparse = np.linspace(v_range[0], v_range[1], 20)\n",
|
||||
"Us, Vs = np.meshgrid(u_sparse, v_sparse)\n",
|
||||
"dUs = np.zeros_like(Us)\n",
|
||||
"dVs = np.zeros_like(Vs)\n",
|
||||
"for i in range(Us.shape[0]):\n",
|
||||
" for j in range(Us.shape[1]):\n",
|
||||
" deriv = neuromorphic_system(0, [Us[i, j], Vs[i, j]], None)\n",
|
||||
" dUs[i, j] = deriv[0]\n",
|
||||
" dVs[i, j] = deriv[1]\n",
|
||||
"\n",
|
||||
"# Normalize for visualization\n",
|
||||
"magnitude = np.sqrt(dUs**2 + dVs**2)\n",
|
||||
"magnitude[magnitude == 0] = 1\n",
|
||||
"dUs_norm = dUs / magnitude\n",
|
||||
"dVs_norm = dVs / magnitude\n",
|
||||
"\n",
|
||||
"ax1.quiver(Us, Vs, dUs_norm, dVs_norm, magnitude, cmap=\"viridis\", alpha=0.6)\n",
|
||||
"\n",
|
||||
"# Fixed points\n",
|
||||
"for fp in fp_neuro:\n",
|
||||
" ax1.plot(\n",
|
||||
" fp[0], fp[1], \"ko\", markersize=10, markerfacecolor=\"yellow\", markeredgewidth=2\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"# Trajectories\n",
|
||||
"t = 50 # np.linspace(0, 50, 2000)\n",
|
||||
"initial_conditions = [\n",
|
||||
" [0.8, 0.2],\n",
|
||||
" [-0.5, 0.5],\n",
|
||||
" [0.3, -0.3],\n",
|
||||
" [1.0, 1.0],\n",
|
||||
" [-1.0, -0.5],\n",
|
||||
" [0.1, 0.8],\n",
|
||||
"]\n",
|
||||
"colors = plt.cm.plasma(np.linspace(0.2, 0.8, len(initial_conditions)))\n",
|
||||
"\n",
|
||||
"for ic, color in zip(initial_conditions, colors):\n",
|
||||
" # sol = odeint(neuromorphic_system, ic, t)\n",
|
||||
" ts, ys = solve(neuromorphic_system, jnp.array(ic), None, t)\n",
|
||||
" ax1.plot(ys[:, 0], ys[:, 1], \"-\", color=color, linewidth=1.5, alpha=0.8)\n",
|
||||
" ax1.plot(ic[0], ic[1], \"o\", color=color, markersize=6)\n",
|
||||
"\n",
|
||||
"ax1.set_xlabel(\"u (Prey analogue)\")\n",
|
||||
"ax1.set_ylabel(\"v (Predator analogue)\")\n",
|
||||
"ax1.set_title(\"Neuromorphic System: Phase Portrait\")\n",
|
||||
"ax1.legend([\"u-nullcline (du/dt=0)\", \"v-nullcline (dv/dt=0)\"], loc=\"upper right\")\n",
|
||||
"ax1.set_xlim(u_range)\n",
|
||||
"ax1.set_ylim(v_range)\n",
|
||||
"ax1.axhline(y=0, color=\"gray\", linestyle=\"--\", alpha=0.3)\n",
|
||||
"ax1.axvline(x=0, color=\"gray\", linestyle=\"--\", alpha=0.3)\n",
|
||||
"\n",
|
||||
"# --- Plot 2: Lotka-Volterra Phase Portrait ---\n",
|
||||
"ax2 = fig.add_subplot(2, 2, 2)\n",
|
||||
"\n",
|
||||
"x_range = [0, 4]\n",
|
||||
"y_range = [0, 4]\n",
|
||||
"X, Y, dX, dY = compute_nullclines_lotka_volterra(x_range, y_range)\n",
|
||||
"\n",
|
||||
"# Nullclines (analytical)\n",
|
||||
"ax2.axvline(x=0, color=\"blue\", linewidth=2, label=\"x-nullcline (x=0)\")\n",
|
||||
"ax2.axhline(\n",
|
||||
" y=a / b,\n",
|
||||
" color=\"blue\",\n",
|
||||
" linewidth=2,\n",
|
||||
" linestyle=\"--\",\n",
|
||||
" label=f\"x-nullcline (y={a / b:.1f})\",\n",
|
||||
")\n",
|
||||
"ax2.axhline(y=0, color=\"red\", linewidth=2, label=\"y-nullcline (y=0)\")\n",
|
||||
"ax2.axvline(\n",
|
||||
" x=c / d,\n",
|
||||
" color=\"red\",\n",
|
||||
" linewidth=2,\n",
|
||||
" linestyle=\"--\",\n",
|
||||
" label=f\"y-nullcline (x={c / d:.1f})\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Vector field\n",
|
||||
"x_sparse = np.linspace(0.1, x_range[1], 18)\n",
|
||||
"y_sparse = np.linspace(0.1, y_range[1], 18)\n",
|
||||
"Xs, Ys = np.meshgrid(x_sparse, y_sparse)\n",
|
||||
"dXs = np.zeros_like(Xs)\n",
|
||||
"dYs = np.zeros_like(Ys)\n",
|
||||
"for i in range(Xs.shape[0]):\n",
|
||||
" for j in range(Xs.shape[1]):\n",
|
||||
" deriv = lotka_volterra(0, [Xs[i, j], Ys[i, j]], None)\n",
|
||||
" dXs[i, j] = deriv[0]\n",
|
||||
" dYs[i, j] = deriv[1]\n",
|
||||
"\n",
|
||||
"magnitude = np.sqrt(dXs**2 + dYs**2)\n",
|
||||
"magnitude[magnitude == 0] = 1\n",
|
||||
"dXs_norm = dXs / magnitude\n",
|
||||
"dYs_norm = dYs / magnitude\n",
|
||||
"\n",
|
||||
"ax2.quiver(Xs, Ys, dXs_norm, dYs_norm, magnitude, cmap=\"viridis\", alpha=0.6)\n",
|
||||
"\n",
|
||||
"# Fixed point\n",
|
||||
"ax2.plot(c / d, a / b, \"ko\", markersize=10, markerfacecolor=\"yellow\", markeredgewidth=2)\n",
|
||||
"\n",
|
||||
"# Trajectories\n",
|
||||
"initial_conditions_lv = [\n",
|
||||
" [0.5, 0.5],\n",
|
||||
" [1.5, 0.5],\n",
|
||||
" [2.5, 1.5],\n",
|
||||
" [0.5, 2.0],\n",
|
||||
" [3.0, 3.0],\n",
|
||||
" [1.0, 3.0],\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"for ic, color in zip(initial_conditions_lv, colors):\n",
|
||||
" ts, ys = solve(lotka_volterra, jnp.array(ic), None, t)\n",
|
||||
" ax2.plot(ys[:, 0], ys[:, 1], \"-\", color=color, linewidth=1.5, alpha=0.8)\n",
|
||||
" ax2.plot(ic[0], ic[1], \"o\", color=color, markersize=6)\n",
|
||||
"\n",
|
||||
"ax2.set_xlabel(\"x (Prey)\")\n",
|
||||
"ax2.set_ylabel(\"y (Predator)\")\n",
|
||||
"ax2.set_title(\"Classic Lotka-Volterra: Phase Portrait\")\n",
|
||||
"ax2.set_xlim([0, x_range[1]])\n",
|
||||
"ax2.set_ylim([0, y_range[1]])\n",
|
||||
"ax2.legend(loc=\"upper right\", fontsize=9)\n",
|
||||
"\n",
|
||||
"ax3 = fig.add_subplot(2, 2, 3)\n",
|
||||
"\n",
|
||||
"t_long = 100\n",
|
||||
"ic_neuro = [0.8, 0.2]\n",
|
||||
"ts, ys = solve(neuromorphic_system, jnp.array(ic_neuro), None, t_long)\n",
|
||||
"\n",
|
||||
"ax3.plot(ts, ys[:, 0], \"b-\", linewidth=2, label=\"u (Prey analogue)\")\n",
|
||||
"ax3.plot(ts, ys[:, 1], \"r-\", linewidth=2, label=\"v (Predator analogue)\")\n",
|
||||
"ax3.set_xlabel(\"Time τ\")\n",
|
||||
"ax3.set_ylabel(\"Population\")\n",
|
||||
"ax3.set_title(\n",
|
||||
" f\"Neuromorphic System: Time Series (IC: u₀={ic_neuro[0]}, v₀={ic_neuro[1]})\"\n",
|
||||
")\n",
|
||||
"ax3.legend()\n",
|
||||
"ax3.axhline(y=0, color=\"gray\", linestyle=\"--\", alpha=0.3)\n",
|
||||
"\n",
|
||||
"ax4 = fig.add_subplot(2, 2, 4)\n",
|
||||
"\n",
|
||||
"ic_lv = [1.5, 0.5]\n",
|
||||
"ts, ys = solve(lotka_volterra, jnp.array(ic_lv), None, t_long)\n",
|
||||
"\n",
|
||||
"ax4.plot(ts, ys[:, 0], \"b-\", linewidth=2, label=\"x (Prey)\")\n",
|
||||
"ax4.plot(ts, ys[:, 1], \"r-\", linewidth=2, label=\"y (Predator)\")\n",
|
||||
"ax4.set_xlabel(\"Time t\")\n",
|
||||
"ax4.set_ylabel(\"Population\")\n",
|
||||
"ax4.set_title(f\"Lotka-Volterra: Time Series (IC: x₀={ic_lv[0]}, y₀={ic_lv[1]})\")\n",
|
||||
"ax4.legend()\n",
|
||||
"\n",
|
||||
"plt.tight_layout()\n",
|
||||
"# plt.savefig('/home/claude/phase_portrait_comparison.png', dpi=150, bbox_inches='tight')\n",
|
||||
"# plt.close()\n",
|
||||
"plt.show()\n",
|
||||
"\n",
|
||||
"print(\"\\n\" + \"=\" * 70)\n",
|
||||
"print(\"3. PHASE PORTRAIT SAVED\")\n",
|
||||
"print(\"=\" * 70)\n",
|
||||
"print(\"Saved: phase_portrait_comparison.png\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1ba0d377",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1600x1000 with 6 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fig2, axes = plt.subplots(2, 3, figsize=(16, 10))\n",
|
||||
"\n",
|
||||
"# --- Bifurcation-like analysis: vary sigma ---\n",
|
||||
"ax = axes[0, 0]\n",
|
||||
"sigmas = [0.01, 0.05, 0.1, 0.2, 0.3]\n",
|
||||
"colors_sigma = plt.cm.coolwarm(np.linspace(0, 1, len(sigmas)))\n",
|
||||
"\n",
|
||||
"for sig, col in zip(sigmas, colors_sigma):\n",
|
||||
"\n",
|
||||
" def neuro_temp(t, state, args):\n",
|
||||
" u, v = state\n",
|
||||
" z = jnp.tanh(kappa * (u - v))\n",
|
||||
" du = z * (1 - alpha * jnp.exp(beta * v) * (1 + gamma * (0.5 - u))) - sig\n",
|
||||
" dv = z * (-1 + alpha * jnp.exp(beta * u) * (1 + gamma * (0.5 - v))) - sig\n",
|
||||
" return jnp.asarray([du, dv])\n",
|
||||
"\n",
|
||||
" ts, ys = solve(neuro_temp, jnp.array([0.5, 0.3]), None, 80)\n",
|
||||
" ax.plot(\n",
|
||||
" ys[:, 0], ys[:, 1], \"-\", color=col, linewidth=1.5, label=f\"σ={sig}\", alpha=0.8\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"ax.set_xlabel(\"u\")\n",
|
||||
"ax.set_ylabel(\"v\")\n",
|
||||
"ax.set_title(\"Neuromorphic: Effect of σ (mortality)\")\n",
|
||||
"ax.legend(fontsize=8)\n",
|
||||
"\n",
|
||||
"ax = axes[0, 1]\n",
|
||||
"kappas = [1, 3, 5, 10, 20]\n",
|
||||
"colors_kappa = plt.cm.viridis(np.linspace(0.2, 0.9, len(kappas)))\n",
|
||||
"\n",
|
||||
"for kap, col in zip(kappas, colors_kappa):\n",
|
||||
"\n",
|
||||
" def neuro_temp(t, state, args):\n",
|
||||
" u, v = state\n",
|
||||
" z = jnp.tanh(kap * (u - v))\n",
|
||||
" du = z * (1 - alpha * jnp.exp(beta * v) * (1 + gamma * (0.5 - u))) - sigma\n",
|
||||
" dv = z * (-1 + alpha * jnp.exp(beta * u) * (1 + gamma * (0.5 - v))) - sigma\n",
|
||||
" return jnp.asarray([du, dv])\n",
|
||||
"\n",
|
||||
" ts, ys = solve(neuro_temp, jnp.array([0.6, 0.2]), None, 60)\n",
|
||||
" ax.plot(\n",
|
||||
" ys[:, 0], ys[:, 1], \"-\", color=col, linewidth=1.5, label=f\"κ={kap}\", alpha=0.8\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"ax.set_xlabel(\"u\")\n",
|
||||
"ax.set_ylabel(\"v\")\n",
|
||||
"ax.set_title(\"Neuromorphic: Effect of κ (switching sharpness)\")\n",
|
||||
"ax.legend(fontsize=8)\n",
|
||||
"\n",
|
||||
"# --- Vary alpha (interaction strength) ---\n",
|
||||
"ax = axes[0, 2]\n",
|
||||
"alphas = [0.2, 0.4, 0.6, 0.8, 1.0]\n",
|
||||
"colors_alpha = plt.cm.plasma(np.linspace(0.2, 0.9, len(alphas)))\n",
|
||||
"\n",
|
||||
"for alp, col in zip(alphas, colors_alpha):\n",
|
||||
"\n",
|
||||
" def neuro_temp(t, state, args):\n",
|
||||
" u, v = state\n",
|
||||
" z = jnp.tanh(kappa * (u - v))\n",
|
||||
" du = z * (1 - alp * jnp.exp(beta * v) * (1 + gamma * (0.5 - u))) - sigma\n",
|
||||
" dv = z * (-1 + alp * jnp.exp(beta * u) * (1 + gamma * (0.5 - v))) - sigma\n",
|
||||
" return jnp.array([du, dv])\n",
|
||||
"\n",
|
||||
" ts, sol = solve(neuro_temp, jnp.array([0.5, 0.3]), None, 60)\n",
|
||||
" ax.plot(\n",
|
||||
" sol[:, 0], sol[:, 1], \"-\", color=col, linewidth=1.5, label=f\"α={alp}\", alpha=0.8\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"ax.set_xlabel(\"u\")\n",
|
||||
"ax.set_ylabel(\"v\")\n",
|
||||
"ax.set_title(\"Neuromorphic: Effect of α (interaction strength)\")\n",
|
||||
"ax.legend(fontsize=8)\n",
|
||||
"\n",
|
||||
"# --- Compare dynamics qualitatively ---\n",
|
||||
"ax = axes[1, 0]\n",
|
||||
"\n",
|
||||
"# Neuromorphic\n",
|
||||
"t_comp, sol1 = solve(neuro_temp, jnp.array([0.7, 0.3]), None, 50)\n",
|
||||
"ax.plot(t_comp, sol1[:, 0], \"b-\", linewidth=2, label=\"Neuro: u\")\n",
|
||||
"ax.plot(t_comp, sol1[:, 1], \"b--\", linewidth=2, label=\"Neuro: v\")\n",
|
||||
"\n",
|
||||
"ax.set_xlabel(\"Time\")\n",
|
||||
"ax.set_ylabel(\"State\")\n",
|
||||
"ax.set_title(\"Neuromorphic Dynamics\")\n",
|
||||
"ax.legend()\n",
|
||||
"\n",
|
||||
"# --- Lotka-Volterra comparison ---\n",
|
||||
"ax = axes[1, 1]\n",
|
||||
"t_comp, sol2 = solve(lotka_volterra, jnp.array([2.0, 1.0]), None, 50)\n",
|
||||
"ax.plot(t_comp, sol2[:, 0], \"r-\", linewidth=2, label=\"LV: x (prey)\")\n",
|
||||
"ax.plot(t_comp, sol2[:, 1], \"r--\", linewidth=2, label=\"LV: y (predator)\")\n",
|
||||
"\n",
|
||||
"ax.set_xlabel(\"Time\")\n",
|
||||
"ax.set_ylabel(\"Population\")\n",
|
||||
"ax.set_title(\"Lotka-Volterra Dynamics\")\n",
|
||||
"ax.legend()\n",
|
||||
"\n",
|
||||
"# --- Summary comparison ---\n",
|
||||
"ax = axes[1, 2]\n",
|
||||
"ax.axis(\"off\")\n",
|
||||
"\n",
|
||||
"summary_text = \"\"\"\n",
|
||||
"COMPARISON SUMMARY\n",
|
||||
"══════════════════════════════════════\n",
|
||||
"\n",
|
||||
"┌─────────────────┬───────────────────┬───────────────────┐\n",
|
||||
"│ Feature │ Neuromorphic │ Lotka-Volterra │\n",
|
||||
"├─────────────────┼───────────────────┼───────────────────┤\n",
|
||||
"│ Interaction │ Exponential │ Bilinear (xy) │\n",
|
||||
"│ │ exp(βx) │ │\n",
|
||||
"├─────────────────┼───────────────────┼───────────────────┤\n",
|
||||
"│ Coupling │ Soft switch │ Direct │\n",
|
||||
"│ │ tanh(κ(u-v)) │ multiplication │\n",
|
||||
"├─────────────────┼───────────────────┼───────────────────┤\n",
|
||||
"│ Equilibrium │ Stable focus or │ Center (neutral │\n",
|
||||
"│ │ limit cycle │ stability) │\n",
|
||||
"├─────────────────┼───────────────────┼───────────────────┤\n",
|
||||
"│ Oscillations │ Damped or │ Persistent │\n",
|
||||
"│ │ sustained │ (conservative) │\n",
|
||||
"├─────────────────┼───────────────────┼───────────────────┤\n",
|
||||
"│ Biological │ More realistic │ Idealized │\n",
|
||||
"│ Realism │ (saturation) │ (no bounds) │\n",
|
||||
"└─────────────────┴───────────────────┴───────────────────┘\n",
|
||||
"\n",
|
||||
"KEY INSIGHT: The neuromorphic system includes\n",
|
||||
"natural saturation mechanisms (tanh, exp bounds)\n",
|
||||
"that prevent unbounded growth - more realistic\n",
|
||||
"for actual biological populations.\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"ax.text(\n",
|
||||
" 0.05,\n",
|
||||
" 0.95,\n",
|
||||
" summary_text,\n",
|
||||
" transform=ax.transAxes,\n",
|
||||
" fontsize=10,\n",
|
||||
" verticalalignment=\"top\",\n",
|
||||
" fontfamily=\"monospace\",\n",
|
||||
" bbox=dict(boxstyle=\"round\", facecolor=\"wheat\", alpha=0.5),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"plt.tight_layout()\n",
|
||||
"# plt.savefig('/home/claude/detailed_comparison.png', dpi=150, bbox_inches='tight')\n",
|
||||
"# plt.close()\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f0539c9f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "felice",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "cbf60fc3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import diffrax as dfx\n",
|
||||
"import jax\n",
|
||||
"import jax.numpy as jnp\n",
|
||||
"import jax.random as jrand\n",
|
||||
"import matplotlib as mpl\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"\n",
|
||||
"from felice.neuron_models import WereRabbit\n",
|
||||
"\n",
|
||||
"jax.config.update(\"jax_enable_x64\", True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "5f28e42f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"key = jrand.key(0)\n",
|
||||
"max_time = 40\n",
|
||||
"\n",
|
||||
"model = WereRabbit(dtype=jnp.float64)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def state_at_t(comp_times):\n",
|
||||
" sol = dfx.diffeqsolve(\n",
|
||||
" terms=dfx.ODETerm(model.dynamics),\n",
|
||||
" solver=dfx.Tsit5(),\n",
|
||||
" t0=0.0,\n",
|
||||
" t1=max_time,\n",
|
||||
" dt0=1e-3,\n",
|
||||
" y0=model.init_state(1)\n",
|
||||
" + jrand.uniform(key, shape=(1, 2), minval=0.1, maxval=0.5),\n",
|
||||
" saveat=dfx.SaveAt(ts=comp_times),\n",
|
||||
" max_steps=100000,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" return sol.ts, sol.ys"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "28924cfa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"comp_times = jnp.linspace(0.0, max_time, 2000)\n",
|
||||
"_, state = state_at_t(comp_times)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "9d4d0829",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def compute_nullclines(snn, u_range, v_range, resolution=200):\n",
|
||||
" \"\"\"\n",
|
||||
" Compute nullclines\n",
|
||||
" du/dt = 0 (u-nullcline)\n",
|
||||
" dv/dt = 0 (v-nullcline)\n",
|
||||
" \"\"\"\n",
|
||||
" u_vals = jnp.linspace(u_range[0], u_range[1], resolution)\n",
|
||||
" v_vals = jnp.linspace(v_range[0], v_range[1], resolution)\n",
|
||||
" U, V = jnp.meshgrid(u_vals, v_vals)\n",
|
||||
"\n",
|
||||
" UV = jnp.stack(\n",
|
||||
" [U.reshape(-1), V.reshape(-1), jnp.ones((resolution * resolution,))], axis=1\n",
|
||||
" )\n",
|
||||
" dS = snn.vector_field(UV)\n",
|
||||
" dU = dS[:, 0].reshape(U.shape)\n",
|
||||
" dV = dS[:, 1].reshape(V.shape)\n",
|
||||
"\n",
|
||||
" return U, V, dU, dV\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def plot_vf(ax, snn, u_range, v_range):\n",
|
||||
" import numpy as np\n",
|
||||
"\n",
|
||||
" u_sparse = jnp.linspace(u_range[0], u_range[1], 20)\n",
|
||||
" v_sparse = jnp.linspace(v_range[0], v_range[1], 20)\n",
|
||||
"\n",
|
||||
" Us, Vs = jnp.meshgrid(u_sparse, v_sparse)\n",
|
||||
"\n",
|
||||
" U, V, dU, dV = compute_nullclines(snn, u_range, v_range, 200)\n",
|
||||
"\n",
|
||||
" UVs = jnp.stack([Us.reshape(-1), Vs.reshape(-1), jnp.ones((20 * 20,))], axis=1)\n",
|
||||
" dS = snn.vector_field(UVs)\n",
|
||||
" dUs = dS[:, 0].reshape(Us.shape)\n",
|
||||
" dVs = dS[:, 1].reshape(Vs.shape)\n",
|
||||
"\n",
|
||||
" # Normalize for visualization\n",
|
||||
" magnitude = np.sqrt(dUs**2 + dVs**2)\n",
|
||||
" magnitude[magnitude == 0] = 1\n",
|
||||
" dUs_norm = dUs / magnitude\n",
|
||||
" dVs_norm = dVs / magnitude\n",
|
||||
"\n",
|
||||
" # Nullclines\n",
|
||||
" ax.contour(U, V, dU, levels=[0], colors=\"blue\", linewidths=1, linestyles=\"-\")\n",
|
||||
" ax.contour(U, V, dV, levels=[0], colors=\"red\", linewidths=1, linestyles=\"-\")\n",
|
||||
"\n",
|
||||
" # Vector field\n",
|
||||
" ax.quiver(Us, Vs, dUs_norm, dVs_norm, magnitude, cmap=\"viridis\", alpha=0.6)\n",
|
||||
"\n",
|
||||
" ax.set_xlabel(\"u (Prey)\")\n",
|
||||
" ax.set_ylabel(\"v (Predator)\")\n",
|
||||
" ax.set_title(\"Wererabbit: Phase Portrait\")\n",
|
||||
" ax.legend([\"u-nullcline (du/dt=0)\", \"v-nullcline (dv/dt=0)\"], loc=\"upper right\")\n",
|
||||
" ax.set_xlim(u_range)\n",
|
||||
" ax.set_ylim(v_range)\n",
|
||||
" ax.axhline(y=0, color=\"gray\", linestyle=\"--\", alpha=0.3)\n",
|
||||
" ax.axvline(x=0, color=\"gray\", linestyle=\"--\", alpha=0.3)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "b1e166ef",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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WQghhBBCCCFE71BdXa2o1WoFUO6++26P5z777DMFUMaNG+da9sorryiAcsUVV3i0/fnnn10ZbB988IHHc1dccYUCKEOGDFEqKip8jmP+/PkeWa7u3DNgIyMjldzcXJ/baGhoUGJjYxVAueWWWxS73e6z3cMPP6wASkxMjNLQ0ODxnHuG3uTJk5Wmpiaf2+iI2tpaJT093edr0na/jEajsnfvXq82BQUFrn0aMmSI1/POrFxAiY+PVwoLC73a7N69WwkICFAA5eKLL/a7jbaZvYrimY3ZNovXF2fbk5nZW1VV5XpN1Gq1kpeX53rOPfMyKChI2bdvn9f6+fn5rgzoRx555LjH19zcrEyaNEkBlL/+9a9ezzsztB988MHj2u7s2bNd+/TTTz/5bFNVVeXKGr733nuPe+wdyexdu3atolKpFEAZMGCAa/kDDzzgWnfOnDle69lsNuXKK690tdm1a5fH8+7HEqDMmzev3bEea5xO3XnO+svsdXJmw0sWr+huktkrhBBCCCGEEALAVQsUvLN13ev1Ojkfr1692qPGrvu67tnC2dnZ/PDDDwC88sorrgzcti666CJXzc3PP//c73gfeughkpOTfT731VdfUVxcjNFo5M033/SZOQnw1FNPYTKZKCkpYdGiRX77evHFF9Hr9X6fP5agoCCuvvpqABYvXtxu23vuuYeMjAyv5QkJCTz++OOAY7KsDRs2+N3GE088QVxcnNfyIUOG8Ic//AGABQsWnNQJtxRFQVGUk1LL12w2s3jxYqZNm0ZxcTEAt912G0lJST7b33fffQwaNMhreWJiIr/61a8A2LRp03GPQ6vVcvPNNwO+31er1Qo43rvj8corrwBw0003ceGFF/psExoa6joevvjiC791rjujrKyMTz75hCuvvNK13b/85S8A2Gw2V53cSy65hCuuuMJrfbVazRtvvIFWqwXgvffe89vXhRdeyMUXX9xlY3fqznNWiN5Egr1CCCGEEEIIIVycwdm9e/d6TE60atUqwDPYO2LECIKDgykvL/e4xd8Z7E1MTCQ9Pd21fOnSpSiKQkBAAOPGjaOurs7vjzPo3F4A7pJLLvH7nLPMwcSJE9FoNH77sdvtriCgv74iIyMZP368376crFYrH3/8MZdffjkpKSkYjUaPSZleeOEFAA4cONDudpwBJl+uueYa1+M1a9ac0DbsdvtxTfjWkz7++GOP1zIoKIjzzz+fLVu2ADB16lTefPNNv+tfdNFFfp8bOHAgQLulEGbPns31119Penq6ayJA588999wD+H5fncfxCy+8wLx583xOZthWfX09a9euBWDatGntnifOydMqKirIyso65rb9aTvxWXR0NNOnT6ekpASAGTNmcMcLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1380x520 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"with mpl.style.context(\"boilerplot.ieeetran\"):\n",
|
||||
" fig, ax = plt.subplots(1, 2, figsize=(6.9, 2.6), dpi=200)\n",
|
||||
" ax[0].plot(comp_times, state[:, 0, 0], label=\"x1\")\n",
|
||||
" ax[0].plot(comp_times, state[:, 0, 1], label=\"x2\")\n",
|
||||
" ax[0].legend([\"x1\", \"x2\"])\n",
|
||||
"\n",
|
||||
" plot_vf(ax[1], model, [-0.2, 0.5], [-0.2, 0.5])\n",
|
||||
" ax[1].plot(state[:, 0, 0], state[:, 0, 1])\n",
|
||||
" ax[1].plot(state[0, 0, 0], state[0, 0, 1], \".\", label=\"start\")\n",
|
||||
" ax[1].plot(state[-1, 0, 0], state[-1, 0, 1], \".\", label=\"end\")\n",
|
||||
" ax[1].legend()\n",
|
||||
" plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f87852ec",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "felice",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
import marimo
|
||||
|
||||
__generated_with = "0.19.4"
|
||||
app = marimo.App(width="medium")
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
import marimo as mo
|
||||
from wigglystuff import Slider2D
|
||||
|
||||
return Slider2D, mo
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
import diffrax
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
return diffrax, jax, jnp, np, plt
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(diffrax, jax, jnp):
|
||||
def vector_field(t, state, args):
|
||||
u, v = state
|
||||
alpha, beta, gamma, kappa, sigma, delta = args
|
||||
|
||||
z = jax.nn.tanh(kappa * (u - v))
|
||||
|
||||
# Prey dynamics
|
||||
du = z * (1 - alpha * jnp.exp(beta * v) * (1 + gamma * (0.5 - u))) - sigma
|
||||
|
||||
# Predator dynamics
|
||||
dv = z * (-1 + alpha * jnp.exp(beta * u) * u * (1 + gamma * (0.5 - v))) - sigma
|
||||
|
||||
return jnp.array([du, dv])
|
||||
|
||||
def vector_field_prod(t, state, args):
|
||||
u, v = state
|
||||
alpha, beta, gamma, kappa, sigma, delta = args
|
||||
|
||||
z = jax.nn.tanh(kappa * (u - v))
|
||||
|
||||
# Prey dynamics
|
||||
du = (
|
||||
z * (1 - alpha * jnp.exp(beta * v) * (1 + gamma * (0.5 - u))) - sigma
|
||||
# + sigma * jnp.maximum(0, delta - u) / (delta + 1e-16)
|
||||
)
|
||||
|
||||
# Predator dynamics
|
||||
dv = (
|
||||
z * (-1 + alpha * jnp.exp(beta * u) * (1 + gamma * (0.5 - v))) - sigma
|
||||
# + sigma * jnp.maximum(0, delta - v) / (delta + 1e-16)
|
||||
)
|
||||
|
||||
dv = jnp.where(jnp.allclose(z, 0.0), dv * jnp.sign(v), dv)
|
||||
du = jnp.where(jnp.allclose(z, 0.0), du * jnp.sign(u), du)
|
||||
|
||||
return jnp.array([du, dv])
|
||||
|
||||
def vector_field_exp(t, state, args):
|
||||
u, v = state
|
||||
alpha, beta, gamma, kappa, sigma, delta = args
|
||||
|
||||
z = jax.nn.tanh(kappa * (u - v))
|
||||
|
||||
# Prey dynamics
|
||||
du = (
|
||||
z * (1 - alpha * jnp.exp(beta * v) * (1 + gamma * (0.5 - u)))
|
||||
- sigma
|
||||
+ sigma * jnp.exp(-u / delta)
|
||||
)
|
||||
|
||||
# Predator dynamics
|
||||
dv = (
|
||||
z * (-1 + alpha * jnp.exp(beta * u) * u * (1 + gamma * (0.5 - v)))
|
||||
- sigma
|
||||
+ sigma * jnp.exp(-v / delta)
|
||||
)
|
||||
|
||||
return jnp.array([du, dv])
|
||||
|
||||
def physical_vector_field(t, state, args):
|
||||
x1, x2 = state
|
||||
alpha, beta, gamma, kappa, sigma, delta = args
|
||||
|
||||
In0 = 129e-15 # fixed by design
|
||||
C = 0.1e-12 # fixed by design
|
||||
kk = 0.39 # fixed by tech
|
||||
Ut = 0.025 # temperature dependent
|
||||
|
||||
Ibias = In0 / alpha
|
||||
|
||||
Ia = Ibias * sigma
|
||||
x3 = jax.nn.tanh(kappa * (x1 - x2))
|
||||
|
||||
dx1 = (
|
||||
x3 * Ibias
|
||||
- (In0 * jnp.exp(kk * x2 / Ut)) * (x3 + 26e-2 * (0.5 - x1) * x3)
|
||||
- Ia
|
||||
) / C
|
||||
dx2 = (
|
||||
-x3 * Ibias
|
||||
+ In0 * jnp.exp(kk * x1 / Ut) * (x3 + 26e-2 * (0.5 - x2) * x3)
|
||||
- Ia
|
||||
) / C
|
||||
|
||||
return jnp.array([dx1, dx2])
|
||||
|
||||
def compute_nullclines(vector_field, u_range, v_range, args, resolution=200):
|
||||
"""
|
||||
Compute nullclines
|
||||
du/dt = 0 (u-nullcline)
|
||||
dv/dt = 0 (v-nullcline)
|
||||
"""
|
||||
alpha, beta, gamma, kappa, sigma, delta = args
|
||||
u_vals = jnp.linspace(u_range[0], u_range[1], resolution)
|
||||
v_vals = jnp.linspace(v_range[0], v_range[1], resolution)
|
||||
U, V = jnp.meshgrid(u_vals, v_vals)
|
||||
|
||||
dU, dV = vector_field(0, [U, V], args)
|
||||
|
||||
return U, V, dU, dV
|
||||
|
||||
def solve(dyn, y0, p, T, n=1000):
|
||||
sol = diffrax.diffeqsolve(
|
||||
diffrax.ODETerm(dyn),
|
||||
diffrax.Tsit5(),
|
||||
t0=0.0,
|
||||
t1=T,
|
||||
dt0=0.01,
|
||||
y0=y0,
|
||||
args=p,
|
||||
saveat=diffrax.SaveAt(ts=jnp.linspace(0, T, n)),
|
||||
stepsize_controller=diffrax.PIDController(rtol=1e-7, atol=1e-8),
|
||||
max_steps=50000,
|
||||
)
|
||||
return sol.ts, sol.ys
|
||||
|
||||
return compute_nullclines, solve, vector_field_prod
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(Slider2D, mo):
|
||||
# alpha = 0.5 # I_n0 / I_bias ratio
|
||||
# beta = 0.39/0.025 # k / U_t (inverse thermal scale)
|
||||
# gamma = 0.26 # coupling coefficient
|
||||
# kappa = 5.0 # tanh steepness
|
||||
# sigma = 0.6 # bias scaling (s * I_bias normalized)
|
||||
# y0 = jnp.array([0.2, 0.4])
|
||||
# ts, ys = solve(vector_field, y0, params, 140)
|
||||
|
||||
alpha = mo.ui.slider(
|
||||
0.0004, 0.012, 0.00001, 0.00129, label="alpha", orientation="vertical"
|
||||
)
|
||||
beta = mo.ui.slider(
|
||||
0.0, 30, 0.00001, 0.39 / 0.025, label="beta", orientation="vertical"
|
||||
)
|
||||
gamma = mo.ui.slider(0, 1, 0.01, 0.26, label="gamma", orientation="vertical")
|
||||
kappa = mo.ui.slider(0, 10, 0.1, 5.0, label="kappa", orientation="vertical")
|
||||
sigma = mo.ui.slider(0, 1, 0.01, 0.6, label="sigma", orientation="vertical")
|
||||
delta = mo.ui.slider(0, 0.1, 0.001, 0.02, label="delta", orientation="vertical")
|
||||
|
||||
# v0 = mo.ui.slider(0, 1.0, 0.01, 0.3, label="v0")
|
||||
# u0 = mo.ui.slider(0, 1.0, 0.01, 0.2, label="u0", orientation="vertical")
|
||||
|
||||
state0 = mo.ui.anywidget(
|
||||
Slider2D(
|
||||
width=150,
|
||||
height=150,
|
||||
x_bounds=(-1.0, 1.5),
|
||||
y_bounds=(-1.0, 1.5),
|
||||
)
|
||||
)
|
||||
|
||||
mo.hstack(
|
||||
[
|
||||
mo.plain_text("""
|
||||
alpha: I_n0 / I_bias ratio
|
||||
beta: k / U_t ratio
|
||||
gamma: coupling coefficient
|
||||
kappa: tanh steepness
|
||||
sigma: bias scaling (s * I_bias)
|
||||
"""),
|
||||
mo.hstack(
|
||||
[state0, alpha, beta, gamma, kappa, sigma, delta], justify="start"
|
||||
),
|
||||
]
|
||||
)
|
||||
return alpha, beta, delta, gamma, kappa, sigma, state0
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(
|
||||
alpha,
|
||||
beta,
|
||||
compute_nullclines,
|
||||
delta,
|
||||
gamma,
|
||||
jnp,
|
||||
kappa,
|
||||
np,
|
||||
plt,
|
||||
sigma,
|
||||
solve,
|
||||
state0,
|
||||
vector_field_prod,
|
||||
):
|
||||
params = (
|
||||
alpha.value,
|
||||
beta.value,
|
||||
gamma.value,
|
||||
kappa.value,
|
||||
sigma.value,
|
||||
delta.value,
|
||||
)
|
||||
ic_neuro = [state0.x, state0.y]
|
||||
|
||||
u_range = [-1.0, 1.5]
|
||||
v_range = [-1.0, 1.5]
|
||||
|
||||
u_sparse = jnp.linspace(u_range[0], u_range[1], 20)
|
||||
v_sparse = jnp.linspace(v_range[0], v_range[1], 20)
|
||||
|
||||
Us, Vs = jnp.meshgrid(u_sparse, v_sparse)
|
||||
|
||||
def plot_vf(ax, vector_field):
|
||||
U, V, dU, dV = compute_nullclines(vector_field, u_range, v_range, params)
|
||||
dUs, dVs = vector_field(0, [Us, Vs], params)
|
||||
|
||||
# Normalize for visualization
|
||||
magnitude = np.sqrt(dUs**2 + dVs**2)
|
||||
magnitude[magnitude == 0] = 1
|
||||
dUs_norm = dUs / magnitude
|
||||
dVs_norm = dVs / magnitude
|
||||
|
||||
# Nullclines
|
||||
ax.contour(U, V, dU, levels=[0], colors="blue", linewidths=2, linestyles="-")
|
||||
ax.contour(U, V, dV, levels=[0], colors="red", linewidths=2, linestyles="-")
|
||||
|
||||
ax.quiver(Us, Vs, dUs_norm, dVs_norm, magnitude, cmap="viridis", alpha=0.6)
|
||||
|
||||
# Trajectories
|
||||
color = plt.cm.plasma(0.2)
|
||||
|
||||
ts, ys = solve(vector_field, jnp.array(ic_neuro), params, 50)
|
||||
ax.plot(ys[:, 0], ys[:, 1], "-", color=color, linewidth=1.5, alpha=0.8)
|
||||
ax.plot(ic_neuro[0], ic_neuro[1], "o", color=color, markersize=6)
|
||||
|
||||
ax.set_xlabel("u (Prey)")
|
||||
ax.set_ylabel("v (Predator)")
|
||||
ax.set_title("Wererabbit: Phase Portrait")
|
||||
ax.legend(["u-nullcline (du/dt=0)", "v-nullcline (dv/dt=0)"], loc="upper right")
|
||||
ax.set_xlim(u_range)
|
||||
ax.set_ylim(v_range)
|
||||
ax.axhline(y=0, color="gray", linestyle="--", alpha=0.3)
|
||||
ax.axvline(x=0, color="gray", linestyle="--", alpha=0.3)
|
||||
|
||||
def plot_trj(ax, vector_field):
|
||||
ts, ys = solve(vector_field, jnp.array(ic_neuro), params, 50)
|
||||
ax.plot(ts, ys[:, 0], "b-", linewidth=2, label="u (Prey)")
|
||||
ax.plot(ts, ys[:, 1], "r-", linewidth=2, label="v (Predator)")
|
||||
|
||||
ax.set_xlabel("Time τ")
|
||||
ax.set_ylabel("Population")
|
||||
ax.set_title(
|
||||
f"Wererabbit: Time Series (IC: u₀={ic_neuro[0]:.2f}, v₀={ic_neuro[1]:.2f})"
|
||||
)
|
||||
ax.legend()
|
||||
ax.axhline(y=0, color="gray", linestyle="--", alpha=0.3)
|
||||
|
||||
fig = plt.figure(figsize=(10, 4))
|
||||
|
||||
# --- Plot 1: Wererabbit Phase Portrait ---
|
||||
ax1 = fig.add_subplot(1, 2, 1)
|
||||
plot_vf(ax1, vector_field_prod)
|
||||
|
||||
ax2 = fig.add_subplot(1, 2, 2)
|
||||
plot_trj(ax2, vector_field_prod)
|
||||
|
||||
# ax3 = fig.add_subplot(3, 2, 3)
|
||||
# plot_vf(ax3, vector_field_prod)
|
||||
|
||||
# ax4 = fig.add_subplot(3, 2, 4)
|
||||
# plot_trj(ax4, vector_field_prod)
|
||||
|
||||
# ax5 = fig.add_subplot(3, 2, 5)
|
||||
# plot_vf(ax5, vector_field_exp)
|
||||
|
||||
# ax6 = fig.add_subplot(3, 2, 6)
|
||||
# plot_trj(ax6, vector_field_exp)
|
||||
|
||||
plt.tight_layout()
|
||||
fig
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
@@ -0,0 +1,158 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "437b0062",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import pandas as pd\n",
|
||||
"import seaborn as sns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "20c6c596",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1000x300 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model_type = \"boomerang\"\n",
|
||||
"task = 1\n",
|
||||
"max_iters = 2000\n",
|
||||
"thr = False\n",
|
||||
"logdir = os.path.join(\"tmp\", f\"task{task}-{max_iters}-{model_type}-{thr}\")\n",
|
||||
"df_mamba = pd.read_pickle(logdir)\n",
|
||||
"\n",
|
||||
"fig, ax = plt.subplots(1, 2, figsize=(10, 3), sharex=True)\n",
|
||||
"sns.lineplot(df_mamba, x=\"Epoch\", y=\"Acc\", hue=\"Model\", ax=ax[0])\n",
|
||||
"sns.lineplot(df_mamba, x=\"Epoch\", y=\"Loss\", hue=\"Model\", ax=ax[1])\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "a222fb0e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"---------- Running in parallel mode ----------\n",
|
||||
"Accuracy: 0.899\n",
|
||||
"---------- Running in sequential mode ----------\n",
|
||||
"Accuracy: 0.9\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import equinox as eqx\n",
|
||||
"import jax\n",
|
||||
"import jax.numpy as jnp\n",
|
||||
"import jax.random as jrandom\n",
|
||||
"from train import Boomerang, Implicit, ReasoningDataset, SequenceClassifier\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@eqx.filter_jit\n",
|
||||
"def compute_accuracy(model, inputs, targets, masks):\n",
|
||||
" def forward_single(inp, tgt, msk):\n",
|
||||
" logits = model(inp)\n",
|
||||
" preds = jnp.argmax(logits, axis=-1)\n",
|
||||
" correct = (preds == tgt) * msk\n",
|
||||
" return correct.sum(), msk.sum()\n",
|
||||
"\n",
|
||||
" correct, total = jax.vmap(forward_single)(inputs, targets, masks)\n",
|
||||
" return correct.sum() / (total.sum() + 1e-8)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@eqx.filter_jit\n",
|
||||
"def compute_accuracy_seq(model, inputs, targets, masks):\n",
|
||||
" def forward_single(inp, tgt, msk):\n",
|
||||
" logits = model.sequential(inp)\n",
|
||||
" preds = jnp.argmax(logits, axis=-1)\n",
|
||||
" correct = (preds == tgt) * msk\n",
|
||||
" return correct.sum(), msk.sum()\n",
|
||||
"\n",
|
||||
" correct, total = jax.vmap(forward_single)(inputs, targets, masks)\n",
|
||||
" return correct.sum() / (total.sum() + 1e-8)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"key = jrandom.key(0)\n",
|
||||
"keys = jrandom.split(key, 2)\n",
|
||||
"\n",
|
||||
"dataset = ReasoningDataset()\n",
|
||||
"\n",
|
||||
"implicit_model = SequenceClassifier(\n",
|
||||
" vocab_size=dataset.VOCAB_SIZE,\n",
|
||||
" d_model=ReasoningDataset.NUM_OUTPUT,\n",
|
||||
" d_state=16,\n",
|
||||
" d_inner=128,\n",
|
||||
" model_class=Implicit if model_type == \"implicit\" else Boomerang,\n",
|
||||
" max_iters=max_iters,\n",
|
||||
" # with_thr=thr,\n",
|
||||
" key=keys[0],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"implicit_model = eqx.tree_deserialise_leaves(f\"{logdir}.eqx\", implicit_model)\n",
|
||||
"\n",
|
||||
"eval_inputs, eval_targets, eval_masks = dataset.generate_batch(\n",
|
||||
" keys[1], 1000, \"simple\" if task == 1 else \"complex\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(10 * \"-\", \" Running in parallel mode \", 10 * \"-\")\n",
|
||||
"acc = compute_accuracy(implicit_model, eval_inputs, eval_targets, eval_masks)\n",
|
||||
"print(\"Accuracy: \", acc)\n",
|
||||
"\n",
|
||||
"print(10 * \"-\", \" Running in sequential mode \", 10 * \"-\")\n",
|
||||
"acc = compute_accuracy_seq(implicit_model, eval_inputs, eval_targets, eval_masks)\n",
|
||||
"print(\"Accuracy: \", acc)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "701c2110",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "felice",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dbc7a212",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Simultaneuos & sequential mode\n",
|
||||
"\n",
|
||||
"In this script we test that both simultaneous and sequential model perform the same output with the Boomerang neuron model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "868157a6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import jax.random as jrandom\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"\n",
|
||||
"from felice.networks.implicit import Boomerang"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "51adce89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = jrandom.randint(jrandom.key(0), (20, 1), 0, 15)\n",
|
||||
"model = Boomerang(\n",
|
||||
" d_model=1,\n",
|
||||
" d_state=16,\n",
|
||||
" d_inner=2,\n",
|
||||
" max_iters=None,\n",
|
||||
" dt=1e-3,\n",
|
||||
" key=jrandom.key(42),\n",
|
||||
")\n",
|
||||
"out_parallel = model(x)\n",
|
||||
"out_sequential = model.sequential(x)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "8af5dc49",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import equinox as eqx\n",
|
||||
"import jax.numpy as jnp\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def loss_fn(model, x):\n",
|
||||
" output = model(x)\n",
|
||||
" return jnp.sum(output[-1])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"gradient = eqx.filter_grad(loss_fn)(model, x)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "3ab81e3c",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1000x300 with 1 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"t = -1\n",
|
||||
"fig, ax = plt.subplots(1, 1, figsize=(10, 3))\n",
|
||||
"ax.plot(out_parallel)\n",
|
||||
"ax.plot(out_sequential, \"--\")\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "0c2e88d4",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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iDM3B0dODs6cHJz5ffEMG4o9JaUHq+w4BRYREZFThD01lfjx44kfP76nh3LSaR0WERERsTwFFhEREbE8BRYRERGxPAUWERERsTwFFhEREbE8BRYRERGxPAUWERERsTwFFhEREbE8BRYRERGxvC4FlocffpiioiI8Hg8lJSWsWrXquNs+/vjjnH/++aSlpZGWlkZpaennbv/DH/4QwzC4//77uzI0ERER6YU6HVhefPFFZsyYwezZs1m3bh2jRo1iypQp1BzngUzLly9n6tSpLFu2jJUrV1JQUMDkyZMpLy8/atsFCxbw/vvvk5eX1/lKREREpNfqdGD5/e9/z/e+9z2uv/56zjzzTB599FHi4+P505/+dMztn3vuOX784x8zevRohg4dyhNPPEEkEmHp0qVHbFdeXs5NN93Ec889h9Pp7Fo1IiIi0it1KrAEAgHWrl1LaWnpZw3YbJSWlrJy5coOtdHc3EwwGCQ9Pb39vUgkwjXXXMPPf/5zhg8f3qF2/H4/DQ0NR7xERESkd+pUYKmrqyMcDpOdnX3E+9nZ2VRVVXWojdtvv528vLwjQs8999yDw+Hg5ptv7vBY5syZQ0pKSvuroKCgw/uKiIjIqeWk3iU0d+5cXnjhBRYsWIDH4wFg7dq1PPDAAzz99NMYhtHhtu644w68Xm/7a//+/bEatoiIiPSwTgWWzMxM7HY71dXVR7xfXV1NTk7O5+573333MXfuXBYvXszIkSPb3//nP/9JTU0N/fr1w+Fw4HA4KCsr47bbbqOoqOi47bndbpKTk494iYiISO/UqcDicrkYN27cERNmP51AO3HixOPud++993LXXXexaNEiiouLj/jsmmuuYcOGDaxfv779lZeXx89//nPeeOONTpYjIiIivZGjszvMmDGD6dOnU1xczIQJE7j//vvx+Xxcf/31AFx77bXk5+czZ84coG1+yqxZs3j++ecpKipqn+uSmJhIYmIiGRkZZGRkHNGH0+kkJyeHM844o7v1iYiISC/Q6cBy1VVXUVtby6xZs6iqqmL06NEsWrSofSLuvn37sNk+O3Ezf/58AoEAV1xxxRHtzJ49mzvvvLN7oxcREZHTgmGaptnTg4iGhoYGUlJS8Hq9ms8iIiJyiujo8VvPEhIRERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHLU2ARERERy1NgEREREctTYBERERHL61JgefjhhykqKsLj8VBSUsKqVauOu+3jjz/O+eefT1paGmlpaZSWlh6xfTAY5Pbbb2fEiBEkJCSQl5fHtddeS0VFRVeGJiIiIr1QpwPLiy++yIwZM5g9ezbr1q1j1KhRTJkyhZqammNuv3z5cqZOncqyZctYuXIlBQUFTJ48mfLycgCam5tZt24dv/rVr1i3bh2vvPIK27dv56tf/Wr3KhMREZFewzBN0+zMDiUlJYwfP5558+YBEIlEKCgo4KabbmLmzJkn3D8cDpOWlsa8efO49tprj7nN6tWrmTBhAmVlZfTr169D42poaCAlJQWv10tycnLHCxIREZEe09Hjd6fOsAQCAdauXUtpaelnDdhslJaWsnLlyg610dzcTDAYJD09/bjbeL1eDMMgNTW1M8MTERGRXsrRmY3r6uoIh8NkZ2cf8X52djbbtm3rUBu33347eXl5R4Sef9Xa2srtt9/O1KlTPzdp+f1+/H5/+9cNDQ0d6l9EREROPSf1LqG5c+fywgsvsGDBAjwez1GfB4NBvvWtb2GaJvPnz//ctubMmUNKSkr7q6CgIFbDFhERkR7WqcCSmZmJ3W6nurr6iPerq6vJycn53H3vu+8+5s6dy+LFixk5cuRRn38aVsrKyliyZMkJ56HccccdeL3e9tf+/fs7U4qIiIicQjoVWFwuF+PGjWPp0qXt70UiEZYuXcrEiROPu9+9997LXXfdxaJFiyguLj7q80/Dys6dO3nzzTfJyMg44VjcbjfJyclHvERERKR36tQcFoALine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 640x480 with 1 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ts, ys = model.get_z(x, t0=2, t1=4, num_points=1000)\n",
|
||||
"plt.figure()\n",
|
||||
"plt.plot(ys[:, :, 0], ys[:, :, 1])\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "79112537",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "felice",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,281 @@
|
||||
import argparse
|
||||
import os
|
||||
from typing import Any, Tuple
|
||||
|
||||
import equinox as eqx
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
import jax.random as jrandom
|
||||
import optax
|
||||
import pandas as pd
|
||||
from jaxtyping import Array, Float
|
||||
from optax import OptState
|
||||
from tqdm import trange
|
||||
|
||||
from felice.datasets.reasoning import ReasoningDataset
|
||||
from felice.networks import Implicit, Mamba, SequenceClassifier
|
||||
from felice.networks.implicit.boomerang import ImplicitBoomerang
|
||||
|
||||
|
||||
def compute_loss(
|
||||
model: eqx.Module, inputs: Array, targets: Array, masks: Array
|
||||
) -> Float[Array, ""]:
|
||||
def forward_single(inp, tgt, msk):
|
||||
logits = model(inp)
|
||||
loss = optax.softmax_cross_entropy_with_integer_labels(logits, tgt)
|
||||
return (loss * msk).sum() / (msk.sum() + 1e-8)
|
||||
|
||||
losses = jax.vmap(forward_single)(inputs, targets, masks)
|
||||
return losses.mean()
|
||||
|
||||
|
||||
v_and_grad = eqx.filter_value_and_grad(compute_loss)
|
||||
|
||||
|
||||
@eqx.filter_jit
|
||||
def compute_accuracy(
|
||||
model: eqx.Module, inputs: Array, targets: Array, masks: Array
|
||||
) -> Float[Array, ""]:
|
||||
def forward_single(inp, tgt, msk):
|
||||
logits = model(inp)
|
||||
preds = jnp.argmax(logits, axis=-1)
|
||||
correct = (preds == tgt) * msk
|
||||
return correct.sum(), msk.sum()
|
||||
|
||||
correct, total = jax.vmap(forward_single)(inputs, targets, masks)
|
||||
return correct.sum() / (total.sum() + 1e-8)
|
||||
|
||||
|
||||
@eqx.filter_jit
|
||||
def train_step(
|
||||
model: eqx.Module,
|
||||
opt_state: OptState,
|
||||
optimizer: Any,
|
||||
inputs: Array,
|
||||
targets: Array,
|
||||
masks: Array,
|
||||
) -> Tuple[eqx.Module, OptState, Array]:
|
||||
loss, grads = v_and_grad(model, inputs, targets, masks)
|
||||
updates, opt_state = optimizer.update(grads, opt_state, model)
|
||||
model = eqx.apply_updates(model, updates)
|
||||
return model, opt_state, loss
|
||||
|
||||
|
||||
def train_and_compare(
|
||||
model_type: Any,
|
||||
logdir: str,
|
||||
task_type: str = "simple",
|
||||
n_epochs: int = 1000,
|
||||
batch_size: int = 64,
|
||||
d_model: int = 64,
|
||||
d_state: int = 16,
|
||||
d_inner: int = 32,
|
||||
dt: float = 1.0,
|
||||
max_iters: int = 8,
|
||||
lr: float = 1e-3,
|
||||
seed: int = 42,
|
||||
# with_thr: bool = True,
|
||||
) -> Tuple[eqx.Module, eqx.Module, Array, Array, pd.DataFrame]:
|
||||
r"""Train Mamba and implicit model on the reasoning synthetic dataset.
|
||||
|
||||
Args:
|
||||
model_type: The type of the implicit model to train (Boomerang, Mamba Implicit).
|
||||
logdir: Directory and filenmae of the log.
|
||||
task_type: Type of task to solve from the reasoning synthetic dataset (simple, accumulation).
|
||||
n_epochs: Number of epochs to train.
|
||||
batch_size: Training batch size.
|
||||
d_model: Model dimensions including output.
|
||||
d_state: Model state dimension.
|
||||
d_inner: Model latent dimension.
|
||||
max_iters: Maximum number of iterations in the implicit model.
|
||||
lr: Learning rate.
|
||||
seed: Random seed.
|
||||
with_thr: For the Boomerang model, if using threshold for dual fixpoints.
|
||||
|
||||
Returns:
|
||||
The trained models (mamba and implicit) with the respective final accuracy and
|
||||
a pandas dataframe with the loss and accuracy per epoch.
|
||||
"""
|
||||
key = jrandom.key(seed)
|
||||
keys = jrandom.split(key, 4)
|
||||
|
||||
dataset = ReasoningDataset()
|
||||
|
||||
standard_model = SequenceClassifier(
|
||||
vocab_size=dataset.VOCAB_SIZE,
|
||||
d_model=d_model,
|
||||
d_state=d_state,
|
||||
d_inner=d_inner,
|
||||
model_class=Mamba,
|
||||
key=keys[0],
|
||||
)
|
||||
|
||||
implicit_model = SequenceClassifier(
|
||||
vocab_size=dataset.VOCAB_SIZE,
|
||||
d_model=d_model,
|
||||
d_state=d_state,
|
||||
d_inner=d_inner,
|
||||
model_class=model_type,
|
||||
max_iters=max_iters,
|
||||
dt=dt,
|
||||
# with_thr=with_thr,
|
||||
key=keys[1],
|
||||
)
|
||||
|
||||
# implicit_model = ImplicitBoomerang(
|
||||
# vocab_size=dataset.VOCAB_SIZE,
|
||||
# d_model=d_model,
|
||||
# d_state=d_state,
|
||||
# d_inner=d_inner,
|
||||
# max_iters=max_iters,
|
||||
# dt=dt,
|
||||
# # with_thr=with_thr,
|
||||
# key=keys[1],
|
||||
# )
|
||||
# Count parameters
|
||||
def count_params(model):
|
||||
return sum(
|
||||
x.size for x in jax.tree_util.tree_leaves(eqx.filter(model, eqx.is_array))
|
||||
)
|
||||
|
||||
print(f"Mamba SSM params: {count_params(standard_model):,}")
|
||||
print(f"Implicit SSM params: {count_params(implicit_model):,}")
|
||||
|
||||
# Optimizers
|
||||
optimizer = optax.adam(lr)
|
||||
standard_opt_state = optimizer.init(eqx.filter(standard_model, eqx.is_array))
|
||||
implicit_opt_state = optimizer.init(eqx.filter(implicit_model, eqx.is_array))
|
||||
|
||||
# Training loop
|
||||
print(f"\nTraining on task: {task_type} with {max_iters} steps")
|
||||
print("=" * 60)
|
||||
|
||||
train_key = keys[2]
|
||||
|
||||
df = pd.DataFrame({"Epoch": [], "Loss": [], "Acc": [], "Model": []})
|
||||
pbar = trange(n_epochs)
|
||||
for epoch in pbar:
|
||||
train_key, batch_key = jrandom.split(train_key)
|
||||
inputs, targets, masks = dataset.generate_batch(
|
||||
batch_key, batch_size, task_type
|
||||
)
|
||||
|
||||
# Train standard model
|
||||
standard_model, standard_opt_state, standard_loss = train_step(
|
||||
standard_model, standard_opt_state, optimizer, inputs, targets, masks
|
||||
)
|
||||
|
||||
# Train implicit model
|
||||
implicit_model, implicit_opt_state, implicit_loss = train_step(
|
||||
implicit_model, implicit_opt_state, optimizer, inputs, targets, masks
|
||||
)
|
||||
|
||||
if (epoch + 1) % 10 == 0:
|
||||
# Evaluate on fresh batch
|
||||
eval_key = jrandom.fold_in(keys[3], epoch)
|
||||
eval_inputs, eval_targets, eval_masks = dataset.generate_batch(
|
||||
eval_key, batch_size, task_type
|
||||
)
|
||||
|
||||
standard_acc = compute_accuracy(
|
||||
standard_model, eval_inputs, eval_targets, eval_masks
|
||||
)
|
||||
implicit_acc = compute_accuracy(
|
||||
implicit_model, eval_inputs, eval_targets, eval_masks
|
||||
)
|
||||
|
||||
new_df = pd.DataFrame(
|
||||
{
|
||||
"Epoch": [epoch, epoch],
|
||||
"Loss": [standard_loss.item(), implicit_loss.item()],
|
||||
"Acc": [standard_acc.item(), implicit_acc.item()],
|
||||
"Model": ["Mamba", "Implicit"],
|
||||
}
|
||||
)
|
||||
df = pd.concat([df, new_df], ignore_index=True)
|
||||
df.to_pickle(logdir)
|
||||
pbar.write(
|
||||
f"Epoch {epoch + 1:4d} | "
|
||||
f"Standard: loss={standard_loss:.4f}, acc={standard_acc:.4f} | "
|
||||
f"Implicit: loss={implicit_loss:.4f}, acc={implicit_acc:.4f}"
|
||||
)
|
||||
|
||||
# Final evaluation
|
||||
print("\n" + "=" * 60)
|
||||
print("Final Evaluation (1000 samples)")
|
||||
print("=" * 60)
|
||||
|
||||
eval_inputs, eval_targets, eval_masks = dataset.generate_batch(
|
||||
keys[4], 1000, task_type
|
||||
)
|
||||
|
||||
standard_acc = compute_accuracy(
|
||||
standard_model, eval_inputs, eval_targets, eval_masks
|
||||
)
|
||||
implicit_acc = compute_accuracy(
|
||||
implicit_model, eval_inputs, eval_targets, eval_masks
|
||||
)
|
||||
|
||||
print(f"Mamba SSM accuracy: {standard_acc:.4f}")
|
||||
print(f"Implicit SSM accuracy: {implicit_acc:.4f}")
|
||||
print(f"Improvement: {(implicit_acc - standard_acc) * 100:.2f}%")
|
||||
|
||||
return standard_model, implicit_model, standard_acc, implicit_acc, df
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-t", type=int, choices=[1, 2], default=1, help="Task to perform"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
type=str,
|
||||
choices=["boomerang", "implicit"],
|
||||
default="implicit",
|
||||
help="Neuron model to use",
|
||||
)
|
||||
parser.add_argument("--dt", type=float, default=0.001, help="Simulation timestep")
|
||||
parser.add_argument("-i", type=int, default=8, help="Maximum number of iterations")
|
||||
parser.add_argument("-b", type=int, default=64, help="Batch size")
|
||||
parser.add_argument(
|
||||
"--thr", action="store_true", help="Using threshold on the boomerang neuron"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.m == "boomerang":
|
||||
model_type = ImplicitBoomerang
|
||||
elif args.m == "implicit":
|
||||
model_type = Implicit
|
||||
else:
|
||||
raise NotImplementedError(f"{args.t} model type not implemented")
|
||||
|
||||
logdir = os.path.join("tmp", f"task{args.t}-{args.i}-{args.m}-{args.thr}")
|
||||
if not os.path.exists("tmp"):
|
||||
os.makedirs("tmp")
|
||||
|
||||
print(f"Saving at {logdir}")
|
||||
_, implicit_model, std_acc1, imp_acc1, df = train_and_compare(
|
||||
model_type,
|
||||
logdir,
|
||||
task_type="simple" if args.t == 1 else "accumulation",
|
||||
n_epochs=1000,
|
||||
batch_size=64,
|
||||
d_model=ReasoningDataset.NUM_OUTPUT,
|
||||
d_state=16,
|
||||
d_inner=128,
|
||||
dt=args.dt,
|
||||
max_iters=args.i,
|
||||
# with_thr=args.thr,
|
||||
)
|
||||
eqx.tree_serialise_leaves(f"{logdir}.eqx", implicit_model)
|
||||
df.to_pickle(logdir)
|
||||
|
||||
print("=" * 70)
|
||||
print("SUMMARY")
|
||||
print("=" * 70)
|
||||
print(f"{'Task':<25} {'Mamba SSM':<15} {'Implicit SSM':<15} {'Delta':<10}")
|
||||
print("-" * 70)
|
||||
print(
|
||||
f"{'Simple Comparison':<25} {std_acc1:<15.4f} {imp_acc1:<15.4f} {(imp_acc1 - std_acc1) * 100:>+.2f}%"
|
||||
)
|
||||
@@ -0,0 +1,305 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 42,
|
||||
"id": "a3496b23",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Equilibrium Point found at: x = 0.4633, y = 0.2943\n",
|
||||
"\n",
|
||||
"--- Stability Analysis ---\n",
|
||||
"Eigenvalues: [-147.60596+8726.844j -147.60596-8726.844j]\n",
|
||||
"Result: System is STABLE at this point.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from functools import partial\n",
|
||||
"\n",
|
||||
"import jax\n",
|
||||
"import jax.numpy as jnp\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import numpy as np\n",
|
||||
"import optimistix as optx\n",
|
||||
"from tqdm import trange\n",
|
||||
"\n",
|
||||
"# --- 1. Define Constants ---\n",
|
||||
"# (Adjust these values based on your specific model needs)\n",
|
||||
"params = {\n",
|
||||
" \"bias\": 100e-12,\n",
|
||||
" \"I0\": 129e-15,\n",
|
||||
" \"a\": 0.39,\n",
|
||||
" \"b\": 0.025,\n",
|
||||
" \"c\": 26e-2,\n",
|
||||
" \"d\": 0.5,\n",
|
||||
" \"scale\": 0.6,\n",
|
||||
" \"C\": 1e-13,\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def system(state, p, input=0):\n",
|
||||
" x, y = state[0], state[1]\n",
|
||||
" z = jnp.tanh(5 * (x - y))\n",
|
||||
"\n",
|
||||
" # Equation for dx/dt\n",
|
||||
" dx = (\n",
|
||||
" input\n",
|
||||
" + z * p[\"bias\"]\n",
|
||||
" - p[\"I0\"] * jnp.exp(p[\"a\"] * y / p[\"b\"]) * (z + p[\"c\"] * (p[\"d\"] - x) * z)\n",
|
||||
" - p[\"bias\"] * p[\"scale\"]\n",
|
||||
" ) / p[\"C\"]\n",
|
||||
"\n",
|
||||
" # Equation for dy/dt\n",
|
||||
" dy = (\n",
|
||||
" -z * p[\"bias\"]\n",
|
||||
" + p[\"I0\"] * jnp.exp(p[\"a\"] * x / p[\"b\"]) * (z + p[\"c\"] * (p[\"d\"] - y) * z)\n",
|
||||
" - p[\"bias\"] * p[\"scale\"]\n",
|
||||
" ) / p[\"C\"]\n",
|
||||
"\n",
|
||||
" return jnp.asarray([dx, dy])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# --- 2. Find Equilibrium Point ---\n",
|
||||
"# We solve for where dx=0 and dy=0\n",
|
||||
"initial_guess = jnp.asarray([0.6, 0.3])\n",
|
||||
"solver = optx.Newton(rtol=1e-3, atol=1e-2)\n",
|
||||
"sol = optx.root_find(\n",
|
||||
" system, solver, initial_guess, args=params, max_steps=100000, throw=False\n",
|
||||
")\n",
|
||||
"fixed_point = sol.value\n",
|
||||
"\n",
|
||||
"print(f\"Equilibrium Point found at: x = {fixed_point[0]:.4f}, y = {fixed_point[1]:.4f}\")\n",
|
||||
"\n",
|
||||
"jacobian_func = jax.jacobian(partial(system, p=params), argnums=0)\n",
|
||||
"J = jacobian_func(fixed_point)\n",
|
||||
"eigenvalues = jnp.linalg.eigvals(J)\n",
|
||||
"\n",
|
||||
"print(\"\\n--- Stability Analysis ---\")\n",
|
||||
"print(f\"Eigenvalues: {eigenvalues}\")\n",
|
||||
"if jnp.all(jnp.real(eigenvalues) < 0):\n",
|
||||
" print(\"Result: System is STABLE at this point.\")\n",
|
||||
"else:\n",
|
||||
" print(\"Result: System is UNSTABLE at this point.\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 47,
|
||||
"id": "7304fea5",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"100%|██████████| 50/50 [00:05<00:00, 9.16it/s]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# --- 4. Plotting the Phase Portrait ---\n",
|
||||
"x_range = jnp.linspace(-1.0, 1.0, 50)\n",
|
||||
"y_range = jnp.linspace(-1.0, 1.0, 50)\n",
|
||||
"X, Y = np.meshgrid(x_range, y_range)\n",
|
||||
"U, V = np.zeros(X.shape), np.zeros(Y.shape)\n",
|
||||
"\n",
|
||||
"EIG = np.zeros(X.shape + (2,), dtype=np.complex64)\n",
|
||||
"\n",
|
||||
"for i in trange(len(x_range)):\n",
|
||||
" for j in range(len(y_range)):\n",
|
||||
" vel = system([X[i, j], Y[i, j]], params, input=100e-12)\n",
|
||||
" U[i, j] = vel[0]\n",
|
||||
" V[i, j] = vel[1]\n",
|
||||
"\n",
|
||||
" # J = jacobian_func(jnp.asarray([X[i,j], Y[i,j]]))\n",
|
||||
" # eigenvalues = jnp.linalg.eigvals(J)\n",
|
||||
" # EIG[i,j,0] = eigenvalues[0]\n",
|
||||
" # EIG[i,j,1] = eigenvalues[1]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 48,
|
||||
"id": "76a4009d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAscAAAIjCAYAAADvI7a6AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnXd8FMX7x997l0tvtBAIvQqoICBIFZCOBQt2BVSsqOjXhhW7YsWKXyvqz96/iigoWAAbAtKR3ntJT67M748nl7tLLsklt1cC8369Nttn5ya7s5995plnDKWUQqPRaDQajUaj0WCJdAY0Go1Go9FoNJpoQYtjjUaj0Wg0Go2mBC2ONRqNRqPRaDSaErQ41mg0Go1Go9FoStDiWKPRaDQajUajKUGLY41Go9FoNBqNpgQtjjUajUaj0Wg0mhK0ONZoNBqNRqPRaErQ4lij0Wg0Go1GoylBi2ONRqM5CjAMg4kTJ0Y6G0clhmEwZcqUSGdDo9EEiBbHGo2m1rFs2TLOOeccmjdvTnx8PFlZWQwZMoTnn38+JNdbuXIlU6ZMYdOmTSFJPxgMwyidLBYLjRs3ZujQocybNy/SWQuacP22mTNnhlW8LliwgClTpnDo0KGwXVOj0QSOFscajaZWsWDBArp3787SpUuZMGECL7zwAldccQUWi4Vp06aF5JorV67k/vvvj0pxDDBkyBDeeecdZsyYwdVXX80///zDoEGD+PbbbyOdtaAJx2+bOXMm999/v2nplaWgoIC77767dH3BggXcf//9WhxrNFFKTKQzoNFoNNXh4YcfJi0tjT///JP09HSffXv27IlMpiJMu3btuPjii0vXzzzzTI4//nieffZZRowYEcGcBU8of1teXh5JSUnVOsfhcOByuYiNjQ34nPj4+OpmTaPRRBBtOdZoNLWK9evX06lTp3LCGCAjI6N0+eSTT6Zz585+02jfvj3Dhg0rXf/ggw/o1q0bKSkppKamctxxx5Vaod966y3GjBkDwMCBA0ub+b2b9r/99lv69etHUlISKSkpjBo1ihUrVvhcc9y4cSQnJ7NlyxZOPfVUkpOTycrK4sUXXwTEVWTQoEEkJSXRvHlz3nvvvRqVD8Bxxx1H/fr12bhxY7l9X3zxBcceeyxxcXF06tSJWbNm+ezfvHkz1157Le3btychIYF69eoxZsyYclZzu93O/fffT9u2bYmPj6devXr07duX2bNn+xy3evVqzjnnHOrWrUt8fDzdu3fnq6++MvW3/fjjj6Xln56ezhlnnMGqVat8zpsyZQqGYbBy5UouvPBC6tSpQ9++fRk3blzp/8DbjQNg06ZNGIbBk08+ybPPPkvr1q2Ji4tj5cqVFBcXc++999KtWzfS0tJISkqiX79+zJ07t1yevX2Op0yZwq233gpAy5YtS68Xra0SGs3RiLYcazSaWkXz5s1ZuHAhy5cv59hjj63wuEsuuYQJEyaUO+7PP/9k7dq1pc3cs2fP5oILLuCUU07h8ccfB2DVqlXMnz+fG2+8kf79+3PDDTfw3HPPceedd9KhQweA0vk777zD2LFjGTZsGI8//jj5+fm8/PLL9O3bl8WLF9OiRYvSazudTkaMGEH//v2ZOnUq//d//8fEiRNJSkrirrvu4qKLLuKss85i+vTpXHrppfTq1YuWLVtWu4wOHjzIwYMHadOmjc/2X3/9lc8++4xrr72WlJQUnnvuOc4++2y2bNlCvXr1SstnwYIFnH/++TRp0oRNmzbx8ssvM2DAAFauXEliYiIgIu/RRx/liiuuoEePHmRnZ/PXX3/x999/M2TIEABWrFhBnz59yMrK4o477iApKYmPPvqI0aNH8+mnn3LmmWcG/dvmzJnDiBEjaNWqFVOmTKGgoIDnn3+ePn368Pfff/uUP8CYMWNo27YtjzzyCEopTjjhBHbs2MHs2bN55513/F7zzTffpLCwkCuvvJK4uDjq1q1LdnY2r732GhdccAETJkwgJyeH119/nWHDhvHHH3/QpUsXv2mdddZZrF27lvfff59nnnmG+vXrA9CgQYNql4VGowkRSqPRaGoR33//vbJarcpqtapevXqp2267TX333XequLjY57hDhw6p+Ph4dfvtt/tsv+GGG1RSUpLKzc1VSil14403qtTUVOVwOCq85scff6wANXfuXJ/tOTk5Kj09XU2YMMFn+65du1RaWprP9rFjxypAPfLII6XbDh48qBISEpRhGOqDDz4o3b569WoFqPvuu6/K8gDU5Zdfrvbu3av27Nmjfv/9d3XKKacoQD311FM+x8XGxqp169aVblu6dKkC1PPPP1+6LT8/v9w1Fi5cqAD19ttvl27r3LmzGjVqVKV5O+WUU9Rxxx2nCgsLS7e5XC7Vu3dv1bZtW1N+W5cuXVRGRobav3+/z++yWCzq0ksvLd123333KUBdcMEF5a5z3XXXKX+vw40bNypApaamqj179vjsczgcqqioyGfbwYMHVcOGDdVll11W7nd4/y+feOIJBaiNGzdWWQYajSb8aLcKjUZTqxgyZAgLFy7k9NNPZ+nSpUydOpVhw4aRlZXl01yflpbGGWecwfvvv49SChDL7Ycffsjo0aNLfU3T09PJy8sr5w4QCLNnz+bQoUNccMEF7Nu3r3SyWq307NnTbxP7FVdcUbqcnp5O+/btSUpK4txzzy3d3r59e9LT09mwYUNA+Xj99ddp0KABGRkZ9OzZk/nz53PzzTczadIkn+MGDx5M69atS9ePP/54UlNTfa6TkJBQumy329m/fz9t2rQhPT2dv//+2yfvK1as4N9///WbpwMHDvDjjz9y7rnnkpOTU1o2+/fvZ9iwYfz7779s3749qN+2c+dOlixZwrhx46hbt67P7xoyZAgzZ84sl97VV19d5TXLcvbZZ5ez7Fqt1lK/Y5fLxYEDB3A4HHTv3t2nnDQaTe1Du1VoNJpax4knnshnn31GcXExS5cu5fPPP+eZZ57hnHPOYcmSJXTs2BGASy+9lA8//JBffvmF/v37M2fOHHbv3s0ll1xSmta1117LRx99xIgRI8jKymLo0KGce+65DB8+vMp8uIXhoEGD/O5PTU31WY+Pjy8nstLS0mjSpEmpn6v39oMHD1ZdGMAZZ5zBxIkTMQyDlJQUOnXq5LejWbNmzcptq1Onjs91CgoKePTRR3nzzTfZvn176YcFwOHDh0uXH3jgAc444wzatWvHsccey/Dhw7nkkks4/vjjAVi3bh1KKe655x7uuecev/nes2cPWVlZNf5tmzdvBuRjoiwdOnTgu+++K9fpriZuKhWdM2PGDJ566ilWr16N3W4P6hoajSZ60OJYo9HUWmJjYznxxBM58cQTadeuHePHj+fjjz/mvvvuA2DYsGE0bNiQd999l/79+/Puu++SmZnJ4MGDS9PIyMhgyZIlfPfdd3z77bd8++23vPnmm1x66aXMmDGj0uu7XC5A/I4zMzPL7Y+J8a1irVar33Qq2u4tTCujSZMmPr+pIgK5zvXXX8+bb77JpEmT6NWrF2lpaRiGwfnnn1/6ewH69+/P+vXr+fLLL/n+++957bXXeOaZZ5g+fTpXXHFF6bG33HKLT+dHb8r6RAfz2wLF2zIezDnvvvsu48aNY/To0dx6661kZGRgtVp59NFHWb9+vRlZ1Wg0EUKLY41Gc0TQvXt3AHbu3Fm6zWq1cuGFF/LWW2/x+OOP88UXXzBhwoRyIjE2NpbTTjuN0047DZfLxbXXXssrr7zCPffcQ5s2bcpZdd24XRQyMjJMFXCR5JNPPmHs2LE89dRTpdsKCwv9xuStW7cu48ePZ/z48eTm5tK/f3+mTJnCFVdcQatWrQCw2WwhK5vmzZsDsGbNmnL7Vq9eTf369QMK1VbR/7cyPvnkE1q1asVnn33mc777w8zs62k0mvChfY41Gk2tYu7cuX4tqm7/0rJN7JdccgkHDx7kqquuIjc31ydmLsD+/ft91i0WS6lrQFFREUCpwCorEIcNG0ZqaiqPPPKIT7O6m71791bjl0UHVqu1XPk+//zzOJ1On21lyy05OZk2bdqUlllGRgYDBgzglVde8flgcWNG2TRq1IguXbowY8YMn//N8uXL+f777xk5cmRA6VT0/60M9weWd1n9/vvvLFy4MCTX02g04UNbjjUaTa3i+uuvJz8/nzPPPJNjjjmG4uJiFixYwIcffkiLFi0YP368z/EnnHACxx57LB9//DEdOnSga9euPvuvuOIKDhw4wKBBg2jSpAmbN2/m+eefp0uXLqXh2rp06YLVauXxxx/n8OHDxMXFMWjQIDIyMnj55Ze55JJL6Nq1K+effz4NGjRgy5YtfPPNN/Tp04cXXnghbGVjBqeeeirvvPMOaWlpdOzYkYULFzJnzpzSUG9uOnbsyIABA+jWrRt169blr7/+4pNPPmHixImlx7z44ov07duX4447jgkTJtCqVSt2797NwoUL2bZtG0uXLg06v0888QQjRoygV69eXH755aWh3NLS0gIeErpbt24A3HDDDQwbNgyr1cr5559f6Tmnnnoqn332GWeeeSajRo1i48aNTJ8+nY4dO5KbmxvQ9e666y7OP/98bDYbp512WrUHJNFoNCEicoEyNBqNpvp8++236rLLLlPHHHOMSk5OVrGxsapNmzbq+uuvV7t37/Z7ztSpU8uFUXPzySefqKFDh6qMjAwVGxurmjVrpq666iq1c+dOn+NeffVV1apVK2W1WsuFdZs7d64aNmyYSktLU/Hx8ap169Zq3Lhx6q+//io9ZuzYsSopKanc9U8++WTVqVOnctubN29eZag0pSRM2HXXXVfj45o3b67Gjh1bun7w4EE1fvx4Vb9+fZWcnKyGDRumVq9eXe64hx56SPXo0UOlp6erhIQEdcwxx6iHH364XEi99evXq0svvVRlZmYqm82msrKy1Kmnnqo++eQT037bnDlzVJ8+fVRCQoJKTU1Line truncated
|
||||
"text/plain": [
|
||||
"<Figure size 800x600 with 1 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fig, ax = plt.subplots(1, 1, figsize=(8, 6))\n",
|
||||
"\n",
|
||||
"lw = jnp.sqrt(U**2 + V**2) / jnp.sqrt(U**2 + V**2).max()\n",
|
||||
"ax.streamplot(X, Y, U, V, color=np.asarray(lw), cmap=\"autumn\")\n",
|
||||
"ax.plot(fixed_point[0], fixed_point[1], \"ro\", label=\"Equilibrium Point\")\n",
|
||||
"ax.set_xlabel(\"x\")\n",
|
||||
"ax.set_ylabel(\"y\")\n",
|
||||
"ax.set_title(\"System Phase Portrait\")\n",
|
||||
"ax.legend()\n",
|
||||
"ax.grid(True)\n",
|
||||
"\n",
|
||||
"# cs = ax.contourf(X, Y, jnp.all(jnp.real(EIG) < 0, axis=2))\n",
|
||||
"\n",
|
||||
"# # # Extract colormap and normalizer\n",
|
||||
"# cmap = cs.cmap\n",
|
||||
"# norm = cs.norm\n",
|
||||
"\n",
|
||||
"# # # Get color for each level (using midpoint of each range)\n",
|
||||
"# colors = [cmap(norm((cs.levels[i] + cs.levels[i+1]) / 2)) for i in range(len(cs.levels)-1)]\n",
|
||||
"\n",
|
||||
"# legend_patches = [Patch(facecolor=colors[0], label=\"real(EIG)>=0\"), Patch(facecolor=colors[1], label=\"real(EIG)<0\")]\n",
|
||||
"# ax.legend(handles=legend_patches, loc='upper right', title='Region')\n",
|
||||
"# cbar = fig.colorbar(cs)\n",
|
||||
"\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 49,
|
||||
"id": "d26bc778",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"System is unstable in 20.5% of the state space.\n",
|
||||
"Conclusion: The system is LOCALLY UNSTABLE but may have stable regions.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# 1. Define a grid of states from -100 to 100\n",
|
||||
"x_vals = jnp.linspace(0, 1, 50)\n",
|
||||
"y_vals = jnp.linspace(0, 1, 50)\n",
|
||||
"X, Y = jnp.meshgrid(x_vals, y_vals)\n",
|
||||
"grid_states = jnp.stack([X.flatten(), Y.flatten()], axis=1)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# 2. Function to get the Max Real Eigenvalue at any state\n",
|
||||
"def get_stability_at_state(state):\n",
|
||||
" # Calculate Jacobian at THIS specific state, not just equilibrium\n",
|
||||
" J = jax.jacobian(system, argnums=0)(state, params)\n",
|
||||
" return jnp.max(jnp.real(jnp.linalg.eigvals(J)))\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def check_divergence(state, params):\n",
|
||||
" # Get Jacobian at an arbitrary state\n",
|
||||
" J = jax.jacobian(system, argnums=0)(state, params)\n",
|
||||
" return jnp.trace(J) # Divergence = sum of diagonal elements\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def lyapunov_derivative(state, eq_point, params):\n",
|
||||
" # 1. Calculate the velocity (f and g)\n",
|
||||
" vel = system(state, params)\n",
|
||||
"\n",
|
||||
" # 2. Gradient of V = 0.5 * (dist^2) is simply the distance vector\n",
|
||||
" grad_V = state - eq_point\n",
|
||||
"\n",
|
||||
" # 3. V_dot = grad_V · velocity (Dot product)\n",
|
||||
" return jnp.dot(grad_V, vel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# 3. Map this over the entire grid\n",
|
||||
"global_stability_map = jax.vmap(get_stability_at_state)(grid_states)\n",
|
||||
"divs = jax.vmap(lambda s: check_divergence(s, params))(grid_states)\n",
|
||||
"\n",
|
||||
"sol = optx.root_find(\n",
|
||||
" system, solver, jnp.asarray([0.6, 0.3]), args=params, max_steps=100000, throw=False\n",
|
||||
")\n",
|
||||
"fixed_point1 = sol.value\n",
|
||||
"\n",
|
||||
"sol = optx.root_find(\n",
|
||||
" system, solver, jnp.asarray([0.3, 0.6]), args=params, max_steps=100000, throw=False\n",
|
||||
")\n",
|
||||
"fixed_point2 = sol.value\n",
|
||||
"\n",
|
||||
"v_dot1 = jax.vmap(lambda s: lyapunov_derivative(s, fixed_point1, params))(grid_states)\n",
|
||||
"v_dot2 = jax.vmap(lambda s: lyapunov_derivative(s, fixed_point2, params))(grid_states)\n",
|
||||
"\n",
|
||||
"# 4. Check for \"Global Instability\"\n",
|
||||
"percentage_unstable = jnp.mean(global_stability_map > 0) * 100\n",
|
||||
"print(f\"System is unstable in {percentage_unstable:.1f}% of the state space.\")\n",
|
||||
"\n",
|
||||
"if percentage_unstable == 100:\n",
|
||||
" print(\"Conclusion: The system is GLOBALLY UNSTABLE.\")\n",
|
||||
"elif percentage_unstable > 0:\n",
|
||||
" print(\"Conclusion: The system is LOCALLY UNSTABLE but may have stable regions.\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 37,
|
||||
"id": "6f1a4b3e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1600x600 with 4 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fig, ax = plt.subplots(1, 2, figsize=(16, 6))\n",
|
||||
"\n",
|
||||
"cs = ax[0].contourf(X, Y, v_dot1.reshape(X.shape))\n",
|
||||
"cbar = fig.colorbar(cs)\n",
|
||||
"ax[0].plot(fixed_point1[0], fixed_point1[1], \"r.\")\n",
|
||||
"\n",
|
||||
"cs = ax[1].contourf(X, Y, v_dot2.reshape(X.shape))\n",
|
||||
"cbar = fig.colorbar(cs)\n",
|
||||
"ax[1].plot(fixed_point2[1], fixed_point2[1], \"r.\")\n",
|
||||
"\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fe176393",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "felice",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Reference in new issue
Block a user