The intuition was the following:
If someone were to look "down" along the circle of the strip, they would see a circle rotating by 180 degrees. A circle of this kind in 2D is very simply parameterized by:
xc(r,ψ)=rcos(ψ)yc(r,ψ)=rsin(ψ)
Now we need to wrap this around a circle. This would mean that the plane the half-circle sits in should be normal to the tangent vector of the circle in 3D.
I choose to wrap my Mobius strip around the axis z with overall radius $R$. So I have the parameterization
x=Rcos(ϕ)y=Rsin(ϕ)z=0
It's not hard to see that at every point on this circle, two basis vector for the plane normal to the tangent vector is given by:
ˆe1=(cosϕ,sinϕ,0)ˆe2=(0,0,1)
With offset
(Rcosϕ,Rsinϕ)
Together, using the equations for (xc,yc), I get the overall parametrization:
xc(r,ϕ)ˆe1(2ϕ)+yc(r,ϕ)ˆe2(2ϕ)+(Rcosϕ,Rsinϕ,0)
Using matplotlib, I have something like:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import interactive
import math
import matplotlib
matplotlib.use('Qt5Agg')
R = 4
def mobius(r, phi):
x_ = r * math.cos(phi)
e1 = np.array([math.cos(2*phi), math.sin(2*phi), 0])
y_ = r * math.sin(phi)
e2 = np.array([0, 0, 1])
return x_*e1 + y_*e2 + np.array([R*math.cos(2*phi), R*math.sin(2*phi),0])
# Generate torus mesh
phi = np.linspace(-0.5*np.pi, 0.5*np.pi, 50)
r = np.linspace(-1, 1, 50)
r, phi = np.meshgrid(r, phi)
X = np.ndarray(r.shape)
Y = np.ndarray(r.shape)
Z = np.ndarray(r.shape)
for (x,y) , r_scalar in np.ndenumerate(r):
phi_scalar = phi[x,y]
result = mobius(r_scalar, phi_scalar)
X[x,y] = result[0];
Y[x,y] = result[1];
Z[x,y] = result[2];
# Display the mesh
# plt.switch_backend('Qt5Agg')
# %matplotlib qt5
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.set_xlim3d(-5, 5)
ax.set_ylim3d(-5, 5)
ax.set_zlim3d(-1, 1)
ax.plot_surface(X, Y, Z, color = 'w', rstride = 1, cstride = 1)
plt.show()
Output looks like: