Skip to content

Commit 98f9f3f

Browse files
CopilotRouthleck
andcommitted
Add documentation for second-order ODEs with JointEq
Co-authored-by: Routhleck <[email protected]>
1 parent 9860d5c commit 98f9f3f

File tree

1 file changed

+177
-2
lines changed

1 file changed

+177
-2
lines changed

docs_classic/tutorial_toolbox/joint_equations.ipynb

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"id": "c9df7780",
2525
"metadata": {},
2626
"source": [
27-
"In a [dynamical system](../tutorial_building/dynamical_systems.ipynb), there may be multiple variables that change dynamically over time. Sometimes these variables are interconnected, and updating one variable requires others as the input. For example, in the widely known Hodgkin–Huxley model, the variables $V$, $m$, $h$, and $n$ are updated synchronously and interdependently (please refer to [Building Neuron Models](../tutorial_building/neuron_models.ipynb)for details). To achieve higher integral accuracy, it is recommended to use ``brainpy.JointEq`` to jointly solving interconnected differential equations."
27+
"In a [dynamical system](../tutorial_building/dynamical_systems.ipynb), there may be multiple variables that change dynamically over time. Sometimes these variables are interconnected, and updating one variable requires others as the input. For example, in the widely known Hodgkin\u2013Huxley model, the variables $V$, $m$, $h$, and $n$ are updated synchronously and interdependently (please refer to [Building Neuron Models](../tutorial_building/neuron_models.ipynb)for details). To achieve higher integral accuracy, it is recommended to use ``brainpy.JointEq`` to jointly solving interconnected differential equations."
2828
]
2929
},
3030
{
@@ -304,6 +304,181 @@
304304
"It is shown in this output code that second differential values of $v$ and $u$ are calculated by using the updated values (`k2_V_arg` and `k2_u_arg`) at the same time. This will result in a more accurate integral."
305305
]
306306
},
307+
{
308+
"cell_type": "markdown",
309+
"id": "second_order_ode_title",
310+
"metadata": {},
311+
"source": [
312+
"## Second-Order ODEs with `brainpy.JointEq`\n",
313+
"\n",
314+
"A common use case for `JointEq` is solving second-order ordinary differential equations (ODEs). ",
315+
"Second-order ODEs appear in many physical systems, such as the harmonic oscillator, pendulum, ",
316+
"or neural mass models like the Jansen-Rit model.\n",
317+
"\n",
318+
"When using `JointEq` for second-order ODEs, it's important to follow the correct function signature pattern."
319+
]
320+
},
321+
{
322+
"cell_type": "markdown",
323+
"id": "second_order_example",
324+
"metadata": {},
325+
"source": [
326+
"### Example: Harmonic Oscillator\n",
327+
"\n",
328+
"Consider a damped harmonic oscillator described by:\n",
329+
"\n",
330+
"$$\\frac{d^2x}{dt^2} = -kx - c\\frac{dx}{dt}$$\n",
331+
"\n",
332+
"To solve this with `JointEq`, we split it into two first-order ODEs:\n",
333+
"\n",
334+
"$$\\frac{dx}{dt} = v$$\n",
335+
"$$\\frac{dv}{dt} = -kx - cv$$\n",
336+
"\n",
337+
"Where $x$ is position and $v$ is velocity."
338+
]
339+
},
340+
{
341+
"cell_type": "code",
342+
"id": "second_order_code",
343+
"metadata": {},
344+
"source": [
345+
"import brainpy as bp\n",
346+
"import brainpy.math as bm\n",
347+
"\n",
348+
"# Parameters\n",
349+
"k = 1.0 # spring constant\n",
350+
"c = 0.1 # damping coefficient\n",
351+
"\n",
352+
"# Define derivative functions\n",
353+
"# IMPORTANT: Each state variable appears as the FIRST parameter before 't'\n",
354+
"# Other state variables appear AFTER 't' as dependencies\n",
355+
"def dx(x, t, v):\n",
356+
" \"\"\"dx/dt = v\"\"\"\n",
357+
" return v\n",
358+
"\n",
359+
"def dv(v, t, x):\n",
360+
" \"\"\"dv/dt = -k*x - c*v\"\"\"\n",
361+
" return -k * x - c * v\n",
362+
"\n",
363+
"# Create joint equation\n",
364+
"joint_eq = bp.JointEq(dx, dv)\n",
365+
"print(f\"Joint equation signature: {joint_eq.__signature__}\")"
366+
],
367+
"outputs": [],
368+
"execution_count": null
369+
},
370+
{
371+
"cell_type": "markdown",
372+
"id": "second_order_signature",
373+
"metadata": {},
374+
"source": [
375+
"### Important: Function Signature Pattern\n",
376+
"\n",
377+
"When defining derivative functions for `JointEq`, follow this pattern:\n",
378+
"\n",
379+
"**Correct:**\n",
380+
"```python\n",
381+
"def dx(x, t, v): # x is the state variable, v is a dependency\n",
382+
" return v\n",
383+
"\n",
384+
"def dv(v, t, x): # v is the state variable, x is a dependency\n",
385+
" return -k * x - c * v\n",
386+
"```\n",
387+
"\n",
388+
"**Incorrect:**\n",
389+
"```python\n",
390+
"def dx(x, v, t): # WRONG: Both x and v before t\n",
391+
" return v\n",
392+
"```\n",
393+
"\n",
394+
"**Rule:** Each state variable should appear as the **first parameter before** `t` in exactly one derivative function. ",
395+
"If a variable is needed as a dependency in another function, it should be placed **after** `t`.\n",
396+
"\n",
397+
"This ensures that `JointEq` knows which variable each function is differentiating and which variables are dependencies."
398+
]
399+
},
400+
{
401+
"cell_type": "markdown",
402+
"id": "second_order_jansen_rit",
403+
"metadata": {},
404+
"source": [
405+
"### Example: Jansen-Rit Model\n",
406+
"\n",
407+
"The Jansen-Rit model is a neural mass model with three coupled second-order ODEs. ",
408+
"Here's how to implement it correctly with `JointEq`:"
409+
]
410+
},
411+
{
412+
"cell_type": "code",
413+
"id": "jansen_rit_code",
414+
"metadata": {},
415+
"source": [
416+
"class JansenRitModel(bp.dyn.NeuDyn):\n",
417+
" def __init__(self, size=1, A=3.25, te=10, B=22, ti=20, C=135, \n",
418+
" e0=2.5, r=0.56, v0=6, method='rk4', **kwargs):\n",
419+
" super().__init__(size=size, **kwargs)\n",
420+
" self.A, self.te = A, te\n",
421+
" self.B, self.ti = B, ti\n",
422+
" self.C = C\n",
423+
" self.e0, self.r, self.v0 = e0, r, v0\n",
424+
" \n",
425+
" # State variables: positions (y0, y1, y2) and velocities (y3, y4, y5)\n",
426+
" self.y0 = bm.Variable(bm.zeros(self.num))\n",
427+
" self.y1 = bm.Variable(bm.zeros(self.num))\n",
428+
" self.y2 = bm.Variable(bm.zeros(self.num))\n",
429+
" self.y3 = bm.Variable(bm.zeros(self.num)) # velocity for y0\n",
430+
" self.y4 = bm.Variable(bm.zeros(self.num)) # velocity for y1\n",
431+
" self.y5 = bm.Variable(bm.zeros(self.num)) # velocity for y2\n",
432+
" \n",
433+
" self.integral = bp.odeint(f=self.derivative, method=method)\n",
434+
" \n",
435+
" # Position derivatives: dx/dt = v\n",
436+
" def dy0(self, y0, t, y3): # y0 is state, y3 is dependency\n",
437+
" return y3 / 1000\n",
438+
" \n",
439+
" def dy1(self, y1, t, y4): # y1 is state, y4 is dependency\n",
440+
" return y4 / 1000\n",
441+
" \n",
442+
" def dy2(self, y2, t, y5): # y2 is state, y5 is dependency\n",
443+
" return y5 / 1000\n",
444+
" \n",
445+
" # Velocity derivatives: dv/dt = ...\n",
446+
" def dy3(self, y3, t, y0, y1, y2): # y3 is state, others are dependencies\n",
447+
" Sp = 2 * self.e0 / (1 + bm.exp(self.r * (self.v0 - y1 + y2)))\n",
448+
" return (self.A * Sp - 2 * y3 - y0 / self.te * 1000) / self.te\n",
449+
" \n",
450+
" def dy4(self, y4, t, y0, y1, inp=0.): # y4 is state, others are dependencies\n",
451+
" Se = 2 * self.e0 / (1 + bm.exp(self.r * (self.v0 - self.C * y0)))\n",
452+
" return (self.A * (inp + 0.8 * self.C * Se) - 2 * y4 - y1 / self.te * 1000) / self.te\n",
453+
" \n",
454+
" def dy5(self, y5, t, y0, y2): # y5 is state, others are dependencies\n",
455+
" Si = 2 * self.e0 / (1 + bm.exp(self.r * (self.v0 - 0.25 * self.C * y0)))\n",
456+
" return (self.B * 0.25 * self.C * Si - 2 * y5 - y2 / self.ti * 1000) / self.ti\n",
457+
" \n",
458+
" @property\n",
459+
" def derivative(self):\n",
460+
" # Join all derivatives - order matches the state variables\n",
461+
" return bp.JointEq([self.dy0, self.dy1, self.dy2, self.dy3, self.dy4, self.dy5])\n",
462+
" \n",
463+
" def update(self, inp=0.):\n",
464+
" y0, y1, y2, y3, y4, y5 = self.integral(\n",
465+
" self.y0, self.y1, self.y2, self.y3, self.y4, self.y5,\n",
466+
" bp.share['t'], inp, bp.share['dt']\n",
467+
" )\n",
468+
" self.y0.value = y0\n",
469+
" self.y1.value = y1\n",
470+
" self.y2.value = y2\n",
471+
" self.y3.value = y3\n",
472+
" self.y4.value = y4\n",
473+
" self.y5.value = y5\n",
474+
"\n",
475+
"# Create and test the model\n",
476+
"model = JansenRitModel(size=1)\n",
477+
"print(\"Jansen-Rit model created successfully!\")"
478+
],
479+
"outputs": [],
480+
"execution_count": null
481+
},
307482
{
308483
"cell_type": "markdown",
309484
"id": "73051bec",
@@ -336,4 +511,4 @@
336511
},
337512
"nbformat": 4,
338513
"nbformat_minor": 5
339-
}
514+
}

0 commit comments

Comments
 (0)