Vector Fields and Streamlines¶
Derive vector fields from symbolic potentials and visualize them with ax_vector_field and ax_streamline.
This notebook highlights the symbolic-numeric bridge: we use Maxima's symbolic differentiation to compute gradient fields from scalar potentials, then feed those expressions directly to ax-plots for numerical sampling and rendering.
load("numerics")$
load("ax-plots")$
Gradient Fields from a Scalar Potential¶
Given a scalar potential $V(x,y)$, the associated conservative field is $\mathbf{F} = -\nabla V$.
We start with the simplest case: a quadratic potential well $V = x^2 + y^2$, whose gradient field points radially inward.
/* Define scalar potential and compute gradient symbolically */
V : x^2 + y^2$
Fx : -diff(V, x)$
Fy : -diff(V, y)$
print("F =", [Fx, Fy])$
F = [-(2*x),-(2*y)]
/* Visualize the gradient field — arrows point toward the origin */
ax_draw2d(
ngrid=12,
ax_vector_field(Fx, Fy, x, -3, 3, y, -3, 3),
title="Gradient of V = x^2 + y^2",
aspect_ratio=true
)$
Electromagnetic Field: Point Charge¶
The electric field of a point charge is $\mathbf{E} = -\nabla V$ where $V = \frac{1}{\sqrt{x^2+y^2}}$.
We add a small regularization term (0.1) to avoid the singularity at the origin, then let Maxima differentiate symbolically.
/* Point charge potential (regularized) */
V_charge : 1/sqrt(x^2 + y^2 + 0.1)$
Ex : -diff(V_charge, x)$
Ey : -diff(V_charge, y)$
print("E =", [Ex, Ey])$
E = [x/(y^2+x^2+0.1)^(3/2),y/(y^2+x^2+0.1)^(3/2)]
/* Electric field radiates outward from the charge */
ax_draw2d(
normalize=true, name="E", ngrid=12,
ax_vector_field(Ex, Ey, x, -3, 3, y, -3, 3),
title="Electric Field: Point Charge",
aspect_ratio=true
)$
Dipole Field¶
A dipole has two charges of opposite sign separated by a distance. The potential is the superposition:
$$V = \frac{1}{\sqrt{(x-1)^2 + y^2 + 0.1}} - \frac{1}{\sqrt{(x+1)^2 + y^2 + 0.1}}$$
Again we derive the field symbolically and plot.
/* Dipole potential: positive charge at (1,0), negative at (-1,0) */
V_dipole : 1/sqrt((x-1)^2 + y^2 + 0.1) - 1/sqrt((x+1)^2 + y^2 + 0.1)$
Dx : -diff(V_dipole, x)$
Dy : -diff(V_dipole, y)$
ax_draw2d(
normalize=true, ngrid=12, name="E",
ax_vector_field(Dx, Dy, x, -4, 4, y, -4, 4),
title="Electric Field: Dipole",
aspect_ratio=true
)$
Fluid Flow: Rotational Field¶
The field $\mathbf{F} = (-y, x)$ describes rigid-body rotation. The vector field and streamlines together form a classic phase portrait.
/* Rotational flow: vector field */
ax_draw2d(
ngrid=12,
ax_vector_field(-y, x, x, -3, 3, y, -3, 3),
title="Rotational Flow: Vector Field",
aspect_ratio=true
)$
/* Rotational flow: streamlines trace circular orbits */
ax_draw2d(
color="#cccccc", ngrid=12, ax_vector_field(-y, x, x, -3, 3, y, -3, 3),
color="red", ax_streamline(-y, x, x, -3, 3, y, -3, 3),
aspect_ratio=true, title="Rotational Flow: Phase Portrait"
)$
Source and Sink Flow¶
A radial source field $\mathbf{F} = \frac{(x, y)}{x^2+y^2+\epsilon}$ points outward from the origin. Streamlines radiate outward like spokes.
/* Source flow: vector field + streamlines */
Sx : x/(x^2 + y^2 + 0.1)$
Sy : y/(x^2 + y^2 + 0.1)$
ax_draw2d(
color="#cccccc", name="F", ngrid=12, ax_vector_field(Sx, Sy, x, -3, 3, y, -3, 3),
color="blue", name="streamline", ax_streamline(Sx, Sy, x, -3, 3, y, -3, 3),
aspect_ratio=true, title="Source Flow"
)$
Curl and Divergence Analysis¶
For a 2D vector field $\mathbf{F} = (P(x,y),\; Q(x,y))$:
- Divergence: $\nabla \cdot \mathbf{F} = \frac{\partial P}{\partial x} + \frac{\partial Q}{\partial y}$ (measures local expansion/compression)
- Curl (scalar in 2D): $(\nabla \times \mathbf{F})_z = \frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y}$ (measures local rotation)
We apply these to our rotational and source flows, computing everything symbolically.
/* Define divergence and curl for 2D fields */
div2d(P, Q) := diff(P, x) + diff(Q, y)$
curl2d(P, Q) := diff(Q, x) - diff(P, y)$
/* Rotational flow: F = (-y, x) */
print("=== Rotational flow F = (-y, x) ===")$
print("div =", div2d(-y, x))$
print("curl =", curl2d(-y, x))$
=== Rotational flow F = (-y, x) === div = 0 curl = 2
/* Source flow: F = (x, y)/(x^2+y^2+0.1) */
print("=== Source flow ===")$
div_source : ratsimp(div2d(Sx, Sy))$
curl_source : ratsimp(curl2d(Sx, Sy))$
print("div =", div_source)$
print("curl =", curl_source)$
=== Source flow === rat: replaced 0.1 by 1/10 = 0.1 rat: replaced 0.1 by 1/10 = 0.1 rat: replaced 0.1 by 1/10 = 0.1 div = 20/(100*y^4+(200*x^2+20)*y^2+100*x^4+20*x^2+1) curl = 0