Skip to content

Commit a00bb9e

Browse files
committed
more dynamical systems updates, more about limit cycles and cobweb demo
1 parent cf14787 commit a00bb9e

File tree

4 files changed

+105
-10
lines changed

4 files changed

+105
-10
lines changed

content/algebra/abstract/groups.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,4 +323,4 @@ $$ Z(G) = \{ z \in G | zg = gz \text{ for all } g \in G \}. $$
323323
The commutator subgroup of $G$ is the group $C$ generated by all elements of the set
324324

325325
$$ \{aba^{-1}b^{-1} | a,b \in G\}. $$
326-
:::
326+
:::

content/applied-math/dynamical-systems/limit-cycles.md

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,71 @@ These @heteroclinic-orbits are much more common in reversible or conservative sy
8888

8989
<!-- TODO: Hamiltonian systems, energy conservation, potential functions, energy level sets, closed orbits -->
9090

91+
## Limit Cycles
9192

92-
## Lyapunov Functions
9393

94-
<!-- TODO: Lyapunov function construction, proving global/asymptotic stability, Lyapunov's theorems -->
94+
:::definition "Limit cycle"
95+
A **limit cycle** is an isolated @closed @trajectory. Isolated means that @neighboring @trajectories are not @closed; they spiral toward or away from the limit cycle.
96+
:::
9597

96-
## Limit Cycles
98+
:::definition "Stable limit cycle" {synonyms: "attracting limit cycle"}
99+
If all neighboring @trajectories approach the @limit-cycle, we say the @limit-cycle is **stable** or **attracting.**
100+
:::
101+
102+
:::definition "Unstable limit cycle"
103+
If a @limit-cycle is not stable, we say it is an **unstable limit cycle**, or in exceptional cases, half-stable.
104+
:::
105+
106+
107+
### Non-Existence Criteria
108+
109+
Sometimes we want to show that a @system does not have a @limit-cycle.
110+
111+
#### Gradient Systems
112+
113+
:::definition "Gradient System"
114+
If a @system can be written in the form $\dot{\vec{x}} = - \nabla V,$ for some @continuously-differentiable, single-values scalar function $V(\vec{x}},$ then it is said to be a **gradient system** with **potential function** $V.$
115+
:::
116+
117+
:::theorem {label: closed-orbits-impossible-in-gradient-systems}
118+
Closed orbits are impossible in gradient systems.
119+
::::intuition
120+
If we're always moving "downhill" in some direction in a space, it's impossible to come back to where we started. This is another reason why oscillations aren't possible in one dimensional systems.
121+
::::
122+
:::
123+
124+
#### Lyapunov Functions
125+
126+
:::definition "Liapunov Function" {synonyms: "Lyapunov function"}
127+
Consider a system $\dot{\vec{x}} = \vec{f(x)}$ with a fixed point at $\vec{x^*}.$ If it has a function with the following properties:
128+
129+
1. $V(\vec{x}) > 0$ for all $\vec{x} \neq \vec{x^*},$ and $V(\vec{x^*}) = 0.$ (i.e. $V$ is @positive-definite.)
130+
131+
2. $\dot{V} < 0$ for all $\vec{x} \neq \vec{x^*}. (All trajectories flow "downhill" toward $\vec{x^*}.$
132+
133+
Then such a function is called a **Liapunov function.**
134+
:::
135+
136+
:::theorem
137+
If a system has a @liapunov-function, then its fixed point $\vec{x^*}$ is globally asymptotically stable: for all initial conditions, $\vec{x}(t) \to \vec{x^*}$ as $t \to \infty.$ Therefore, the system has no closed orbits.
138+
::::intuition
139+
Like gradient systems, we can't get in a loop if we're always moving downhill.
140+
::::
141+
:::
142+
143+
There is no systematic way to construct @Lianpunov-functions. Strogatz says Divine Inspiration is required.
144+
145+
#### Dulac's Criterion
146+
147+
Dulac's Criterion is based on @greens-theorem.
148+
149+
:::theorem
150+
Let $\dot{\vec{x}} = \vec{f(x)}$ be a @continuously-differentiable @vector-field defined on a @simply-connected @subset $R$ of the @plane. If there exists a @continuously-differentiable, @real-valued @function $g(\vec{x})$ such that $\nabla \cdot (g\dot{\vec{x}})$ has one sign throughout $R,$ then there are no @closed @orbits lying entirely in $R.$
151+
:::
152+
153+
The special case where $g(\vec{x}) = 1$ is called **Bendixson's criterion.**
154+
155+
As with @Liapunov-functions, there is no algorithm for finding $g(\vec{x}).$
97156

98157
<!-- TODO: definition, isolated closed orbits, stable/unstable/half-stable limit cycles, amplitude and frequency -->
99158

content/applied-math/dynamical-systems/planar.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,3 @@ $$ f(x^*, y^*) = 0, \quad g(x^*, y^*) = 0. $$
8585
We can do linear stability analysis on this system to understand the local behavior near fixed points. We use the @Jacobian matrix of the system, then plug in $(x^*, y^*).$ For the nonlinear case, we have the same geometric interpretations as the linear case. Topologically, if the real part of any eigenvalue of a fixed point is positive, then the fixed point is unstable. If the real part of all eigenvalues of a fixed point is negative, then the fixed point is asymptotically stable. We call these cases (both eigenvalues have nonzero real parts) hyperbolic @fixed points
8686

8787
However, if the real part of any eigenvalue is zero, this linearized approached does not tell us about the stability of the fixed point.
88-
89-
90-
<!-- TODO: converting planar systems to polar form, radial and angular dynamics, using polar coordinates for limit cycle analysis -->
91-

demos/dynamical-systems/cobweb.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import p5 from 'p5';
33
import type { DemoConfig, DemoInstance } from '@framework/types';
44
import { P5DemoBase } from '@framework';
55
import type { DemoMetadata } from '@framework';
6+
import * as math from 'mathjs';
67
import { parse, derivative, evaluate } from 'mathjs';
78
import type { MathNode, EvalFunction } from 'mathjs';
89

@@ -19,7 +20,7 @@ interface CobwebPreset {
1920
}
2021

2122
const COBWEB_PRESETS: CobwebPreset[] = [
22-
{ label: 'Cubic', expr: '3*x - x^3', rMin: 0, rMax: 1, rDefault: 1, rStep: 0.01, xMin: -2, xMax: 2, x0Default: 0.5 },
23+
{ label: 'Cubic', expr: 'r*x - x^3', rMin: 0, rMax: 5, rDefault: 3, rStep: 0.01, xMin: -2, xMax: 2, x0Default: 0.5 },
2324
{ label: 'Logistic', expr: 'r*x*(1-x)', rMin: 0, rMax: 4, rDefault: 2.8, rStep: 0.01, xMin: 0, xMax: 1, x0Default: 0.1 },
2425
{ label: 'Quadratic', expr: 'x^2 + r', rMin: -2, rMax: 0.25, rDefault: -0.5, rStep: 0.01, xMin: -2, xMax: 2, x0Default: 0.5 },
2526
{ label: 'Sine', expr: 'r*sin(x)', rMin: 0, rMax: 3, rDefault: 1.5, rStep: 0.01, xMin: 0, xMax: 3.15, x0Default: 0.5 },
@@ -36,6 +37,7 @@ class CobwebDemo extends P5DemoBase {
3637
private compiledF: EvalFunction | null = null;
3738
private compiledDf: EvalFunction | null = null;
3839
private parseError: boolean = false;
40+
private parseWarning: string = '';
3941
private exprString: string = 'r*x*(1-x)';
4042
private r: number = 2.8;
4143

@@ -74,6 +76,7 @@ class CobwebDemo extends P5DemoBase {
7476
private startBtn!: HTMLButtonElement;
7577
private stepBtn!: HTMLButtonElement;
7678
private iterationDisplay!: HTMLSpanElement;
79+
private warningEl!: HTMLSpanElement;
7780

7881
constructor(container: HTMLElement, config?: DemoConfig) {
7982
super(container, config, metadata);
@@ -90,6 +93,22 @@ class CobwebDemo extends P5DemoBase {
9093
try {
9194
const node: MathNode = parse(exprString);
9295
this.compiledF = node.compile();
96+
// Check for unknown function calls (e.g. "r(-x)" parsed as function call to "r")
97+
const allowedVars = new Set(['x', 'r']);
98+
const unknownFns: string[] = [];
99+
node.traverse((n: MathNode) => {
100+
if (n.type === 'FunctionNode' && typeof (n as any).name === 'string') {
101+
const name = (n as any).name as string;
102+
if (allowedVars.has(name) && typeof (math as any)[name] !== 'function') {
103+
unknownFns.push(name);
104+
}
105+
}
106+
});
107+
if (unknownFns.length > 0) {
108+
this.parseWarning = `"${unknownFns[0]}(...)" is being parsed as a function call — did you mean "${unknownFns[0]}*(...)"?`;
109+
} else {
110+
this.parseWarning = '';
111+
}
93112
try {
94113
const dfNode = derivative(node, 'x');
95114
this.compiledDf = dfNode.compile();
@@ -101,6 +120,7 @@ class CobwebDemo extends P5DemoBase {
101120
return true;
102121
} catch {
103122
this.parseError = true;
123+
this.parseWarning = '';
104124
this.compiledF = null;
105125
this.compiledDf = null;
106126
return false;
@@ -242,6 +262,10 @@ class CobwebDemo extends P5DemoBase {
242262
private updateFunction(): void {
243263
const success = this.parseExpression(this.exprString);
244264
this.inputEl.style.borderColor = success ? '' : 'red';
265+
if (this.warningEl) {
266+
this.warningEl.textContent = this.parseWarning;
267+
this.warningEl.style.display = this.parseWarning ? 'block' : 'none';
268+
}
245269
this.computeViewRange();
246270
this.applyZoom();
247271
this.findFixedPoints();
@@ -659,6 +683,13 @@ class CobwebDemo extends P5DemoBase {
659683

660684
panel.appendChild(row1);
661685

686+
this.warningEl = document.createElement('span');
687+
this.warningEl.style.color = '#cc6600';
688+
this.warningEl.style.fontSize = 'var(--font-size-sm)';
689+
this.warningEl.style.fontFamily = 'var(--font-mono, monospace)';
690+
this.warningEl.style.display = 'none';
691+
panel.appendChild(this.warningEl);
692+
662693
// Row 2: Parameter r
663694
const row2 = this.makeRow();
664695
row2.appendChild(this.makeLabel('r ='));
@@ -685,7 +716,16 @@ class CobwebDemo extends P5DemoBase {
685716
try {
686717
const val = evaluate(this.rInputEl.value);
687718
if (typeof val === 'number' && !isNaN(val)) {
688-
this.rSliderEl.value = Math.max(parseFloat(this.rSliderEl.min), Math.min(parseFloat(this.rSliderEl.max), val)).toString();
719+
// Auto-expand slider range if value is outside current bounds
720+
if (val > parseFloat(this.rSliderEl.max)) {
721+
this.rSliderEl.max = val.toString();
722+
this.rMaxInputEl.value = val.toString();
723+
}
724+
if (val < parseFloat(this.rSliderEl.min)) {
725+
this.rSliderEl.min = val.toString();
726+
this.rMinInputEl.value = val.toString();
727+
}
728+
this.rSliderEl.value = val.toString();
689729
this.rInputEl.style.borderColor = '';
690730
this.r = val;
691731
this.updateParameter();

0 commit comments

Comments
 (0)