diff --git a/_posts/2025-09-29-Separatrix-Locator.md b/_posts/2025-09-29-Separatrix-Locator.md
index 38e7e20b..6b82d172 100644
--- a/_posts/2025-09-29-Separatrix-Locator.md
+++ b/_posts/2025-09-29-Separatrix-Locator.md
@@ -98,6 +98,8 @@ Separatrices! These are boundaries between basins of attraction in dynamical sys
**Code**: We provide a Python package implementing this method at [github.com/KabirDabholkar/separatrix_locator](https://github.com/KabirDabholkar/separatrix_locator).
+**Interactive version**: if the interactive plots below don't work click [here](https://unireps.github.io/blog/2025/Separatrix-Locator/).
+
## Introduction: Fixed Points and Beyond
Many natural and artificial systems — from neural circuits making decisions to ecosystems switching between healthy and diseased states — are modelled as **multistable dynamical systems**. Their behaviour is governed by multiple **attractors** in state space, each corresponding to a stable mode of activity. Understanding these systems often boils down to understanding their **geometry**: where are the stable states, and how are the different basins of attraction separated?
@@ -106,8 +108,9 @@ For the last decade, a workhorse of neural circuit analysis has been **fixed poi
### Finding Fixed Points
First consider a bistable dynamical system in 2 dimensions. Below is a phase-portrait of such a system, with two stable fixed points and one unstable fixed point. Click on plot to realise trajectories of the dynamics.
+[(click here if the plot below doesn't load)](https://unireps.github.io/blog/2025/Separatrix-Locator/)
-
@@ -119,8 +122,9 @@ Now try to click somewhere that will lead you exactly to the saddle point. Did y
This motivates developing a principled way to find such points. One solution is to define a specific scalar function of the dynamics whose only minima are given by all the fixed points. One such function is the kinetic energy $$q(\boldsymbol x)=\frac{1}{2}\Vert f(\boldsymbol x)\Vert^2$$ . By differentiating this function, one can perform gradient descent to find these minima. The interactive plot below realises such trajectories.
+[(click here if the plot below doesn't load)](https://unireps.github.io/blog/2025/Separatrix-Locator/)
-
@@ -136,8 +140,9 @@ When a system receives a perturbation — for example, a sensory input or an opt
Below is an example of such a function that we constructed for this simple system (click on it to run trajectories).
+[(click here if the plot below doesn't load)](https://unireps.github.io/blog/2025/Separatrix-Locator/)
-
@@ -166,7 +171,7 @@ where $$\dot \square$$ is shorthand for the time derivative $$\frac{d}{dt}\squar
Any bistable system can be decomposed as follows: it will have two attractors, their respective basins of attraction and the separatrix between them. This is like a cheese sandwich: the attractors are slices of bread, and the separatrix is the slice of cheese between them. We can call this the **Sandwich of Bistability**. In general, this sandwich could be arbitrarily oriented in $$\mathbb R^N$$ and even nonlinearly warped.
-
+
The Sandwich of Bistability: Two attractors and their basins of attraction (bread slices) separated by a separatrix (cheese slice). We only care about mapping the coordinates along bistable axis.
@@ -187,7 +192,7 @@ $$
$$
-
+
@@ -195,7 +200,7 @@ $$
with $$\lambda>0$$, dropping the $$\boldsymbol x$$ notation for a moment for clarity. This system has fixed point attractors at $$\pm 1$$ and an unstable fixed point (a separatrix) at $$0$$ -- a 1D Sandwich of Bistability.
-
+
Mapping the high-D state to a 1D bistable system.
@@ -487,7 +492,7 @@ Now that we know what we are looking for (PDE equation), and how to find it (DNN
We train a DNN on a bistable damped oscillator, and on a 2D GRU trained on a 1-bit flip-flop task. In both cases, the resulting $$\psi$$ has a zero level set on the separatrix.
-
+
A: ODEs for the damped duffing oscillator. B: Kinetic energy function identifies stable and unstable fixed points. C: DNN approximation of the sKEF and it's level sets. The zero-level set (orange) aligns with the separatrix. D,E,F: Same for a 2D GRU RNN trained on a 1-bit flip flop task.
@@ -496,7 +501,7 @@ We train a DNN on a bistable damped oscillator, and on a 2D GRU trained on a 1-b
Finally, we take a published $$N=668$$ unit RNN trained to reproduce the activity of neurones from anterior lateral motor cortex of mice trained to respond to optogenetic stimulation of their somatosensory cortex . By simulating the RNN we can locate the two attractors. The separatrix is an $$(N-1)$$-dimensional manifold in $$\mathbb{R}^N$$. To evaluate our method, we sample this high-D space by drawing random cubic Hermite curves that connect the two attractors (Fig. **A**). We then run many simulations via a binary-search along each curve (parameterised by $$\alpha\in[0,1]$$) to find the true separatrix crossing, and compare with $$\psi=0$$, finding close agreement (Fig. **B**). This also allows us to design optimal perturbations. If we want to change the network's decision, pushing the system towards the desired attractor may not be the most efficient direction. Using $$\psi$$, we design minimal perturbations that cross the separatrix. The resulting perturbation size is smaller than perturbations aimed at the target fixed point or random separatrix locations (Fig. **C**).
-
+
A: Hermite curves connecting attractors of a data-trained RNN (2D projection from 668D) with true separatrix points (red). B: sKEF zeroes versus true separatrix points along each curve. C: Norm of perturbations to reach separatrix from base point $\boldsymbol{x}_\text{base}$.
diff --git a/assets/html/2025-09-29-Separatrix-Locator/absolute_value_gradient_descent.html b/assets/html/2025-09-29-Separatrix/absolute_value_gradient_descent.html
similarity index 81%
rename from assets/html/2025-09-29-Separatrix-Locator/absolute_value_gradient_descent.html
rename to assets/html/2025-09-29-Separatrix/absolute_value_gradient_descent.html
index b5d729c8..b48431bc 100644
--- a/assets/html/2025-09-29-Separatrix-Locator/absolute_value_gradient_descent.html
+++ b/assets/html/2025-09-29-Separatrix/absolute_value_gradient_descent.html
@@ -1,12 +1,13 @@
+
- Gradient Descent on Modified Energy Function
+ Separatrix Locator
+
-
Gradient Descent on |x+y|
+
Separatrix Locator
@@ -133,10 +165,9 @@
Gradient Descent on |x+y|
-
-
+
Shift+Drag to pan · Double‑click to recenter · Click to draw trajectories
@@ -148,39 +179,39 @@
Gradient Descent on |x+y|
// ========================================================================
// DYNAMICAL SYSTEM DEFINITION
// ========================================================================
-
+
// Energy function
function q(x, y) {
return Math.abs(x + y);
}
-
+
// Gradient descent dynamics with smoothing near singularities
const SYSTEM = {
f: (x, y) => {
const eps = 1e-6; // smoothing parameter for numerical stability
const u = x + y;
-
+
// Smooth approximation of sign function near zero
// grad(|u|) = sign(u), but we smooth it near u=0
const smoothSign = u / Math.sqrt(u * u + eps * eps);
-
- return {
+
+ return {
dx: -smoothSign,
dy: -smoothSign
};
},
-
+
equations: {
dx: '−∂f/∂x',
dy: '−∂f/∂y'
},
-
+
energy: 'f(x,y) = |x+y|'
};
// ========================================================================
// Update title with system equations
- document.getElementById('systemDesc').innerHTML =
+ document.getElementById('systemDesc').innerHTML =
'Click anywhere to trace the trajectory forward in time from that initial condition.';
// ---------- Viewport / transforms ----------
@@ -193,16 +224,17 @@
Gradient Descent on |x+y|
dt: 0.02, nsteps: 4000, eps: 1e-6,
gx: 28, gy: 28,
trajectories: [],
- pan: {active: false, sx:0, sy:0, ox:0, oy:0},
+ pan: { active: false, sx: 0, sy: 0, ox: 0, oy: 0 },
+ animating: false,
};
function rk4Step(x, y, h) {
const k1 = SYSTEM.f(x, y);
- const k2 = SYSTEM.f(x + 0.5*h*k1.dx, y + 0.5*h*k1.dy);
- const k3 = SYSTEM.f(x + 0.5*h*k2.dx, y + 0.5*h*k2.dy);
- const k4 = SYSTEM.f(x + h*k3.dx, y + h*k3.dy);
- const nx = x + (h/6)*(k1.dx + 2*k2.dx + 2*k3.dx + k4.dx);
- const ny = y + (h/6)*(k1.dy + 2*k2.dy + 2*k3.dy + k4.dy);
+ const k2 = SYSTEM.f(x + 0.5 * h * k1.dx, y + 0.5 * h * k1.dy);
+ const k3 = SYSTEM.f(x + 0.5 * h * k2.dx, y + 0.5 * h * k2.dy);
+ const k4 = SYSTEM.f(x + h * k3.dx, y + h * k3.dy);
+ const nx = x + (h / 6) * (k1.dx + 2 * k2.dx + 2 * k3.dx + k4.dx);
+ const ny = y + (h / 6) * (k1.dy + 2 * k2.dy + 2 * k3.dy + k4.dy);
return { x: nx, y: ny };
}
@@ -239,7 +271,7 @@