Inertial control of the a spinning flat disk

Luc Jaulin












This microsite is associated to the paper

L. Jaulin (2023). Inertial control of the a spinning flat disk



This work proposes a Lyapunov based approach to control the rotation of a flat disk spinning in the space without external forces. The motion of the disk is governed by the Euler's rotation equation for spinning objects. The control is made through the inertia matrix of the disk..







An illustration of the change of rotation without external forces :





Code to simulate the spinning wheel




from roblib import *  # available at https://www.ensta-bretagne.fr/jaulin/roblib.py


def draw_wheel(R,I,wr,colorwheel="black"):
    clean3D(ax,-3,3,-3,3,-1,5)
    w=R@wr
    L=R@I@wr # angular momentum
    draw_arrow3D(ax,0,0,2,0.02*L[0,0],0.02*L[1,0],0.02*L[2,0],"red")
    draw_arrow3D(ax,0,0,2,0.1*w[0,0],0.1*w[1,0],0.1*w[2,0],"green") # spin vector
    l2,l3=sqrt(abs(I[2,2]-I30+0.5)),sqrt(abs((I[1,1]-I20+0.5)))
    M=tran3H(0,0,2)@ToH(R)@wheel3H(r)
    draw3H(ax,M,colorwheel,True,1)
    def draw_masses(l2,l3,col):
        p=array([[0],[0],[2]])+R@array([[0],[l2],[l3]])
        ax.scatter(*p,color=col)
    draw_masses(l2,0,'green')
    draw_masses(-l2,0,'green')
    draw_masses(0, l3,'black')
    draw_masses(0,-l3,'black')
    pause(0.001)

def f(x,u):
    w1,w2,w3,I2,I3=x[0,0],x[1,0],x[2,0],x[3,0],x[4,0]
    I1=I2+I3
    u1,u2=u[0,0],u[1,0]
    dw1=-((I3-I2)/I1)*w2*w3-w1*(u1+u2)/I1
    dw2=-w3*w1-w2*u1/I2
    dw3= w1*w2-w3*u2/I3
    dI2=u1
    dI3=u2
    return array([[dw1],[dw2],[dw3],[dI2],[dI3]])


ax= Axes3D(figure())
dt=0.002
r=1
m=10
R=eye(3)
w=array([[10],[4],[1]])
wr=R.T@w
I20,I30=1/4*m*r**2,1/4*m*r**2
x=array([[wr[0,0]],[wr[1,0]],[wr[2,0]],[I20],[I30]])

for t in arange(0,1,dt):
    I2,I3=x[3,0],x[4,0]
    I1=I2+I3
    I=diag([I1,I2,I3])
    wr=array([[x[0,0]],[x[1,0]],[x[2,0]]])
    draw_wheel(R,I,wr)
    u=array([[0],[0]])
    R=R@expw(dt*wr)
    x=array([[wr[0,0]],[wr[1,0]],[wr[2,0]],[I2],[I3]])
    x=x+dt*f(x+(dt/2)*f(x,u),u)








Illustration of the different controllers




from sympy import *
from sympy.diffgeom import *
from roblib import *  # available at https://www.ensta-bretagne.fr/jaulin/roblib.py


def draw_wheel(R,I,wr,colorwheel="black"):
    clean3D(ax,-3,3,-3,3,-1,5)
    w=R@wr
    L=R@I@wr # angular momentum
    draw_arrow3D(ax,0,0,2,0.05*L[0,0],0.05*L[1,0],0.05*L[2,0],"red")
    draw_arrow3D(ax,0,0,2,0.3*w[0,0],0.3*w[1,0],0.3*w[2,0],"green") # spin vector
    l2,l3=sqrt(abs(I[2,2]-I30+0.5)),sqrt(abs((I[1,1]-I20+0.5)))
    M=tran3H(0,0,2)@ToH(R)@wheel3H(r)
    draw3H(ax,M,colorwheel,True,1)
    def draw_masses(l2,l3,col):
        p=array([[0],[0],[2]])+R@array([[0],[l2],[l3]])
        ax.scatter(*p,color=col)
    draw_masses(l2,0,'green')
    draw_masses(-l2,0,'green')
    draw_masses(0, l3,'black')
    draw_masses(0,-l3,'black')
    pause(0.001)


def Symb_build(opt):
    init_printing(use_unicode=True) # allow LaTeX printing
    w1,w2,w3,I2,I3=symbols("w1 w2 w3 I2 I3")
    I20,I30=symbols("I20 I30")
    C=CoordSystem('C',Patch('P',Manifold('M',5)),[w1,w2,w3,I2,I3])
    w1,w2,w3,I2,I3 = C.coord_functions()
    E=C.base_vectors()
    F=-((I3-I2)/(I2+I3))*w2*w3*E[0]+-w3*w1*E[1]+w1*w2*E[2]
    G1=-(w1/(I2+I3))*E[0]-(w2/I2)*E[1]+E[3]
    G2=-(w1/(I2+I3))*E[0]-(w3/I3)*E[2]+E[4]
    V0=1/2*(I2-I20)**2 + 1/2*(I3-I30)**2
    if opt=="align": V = 1/2*(w2)**2 + 1/2*(w3)**2 + V0
    if opt=="passiv": V = 1/2*(I2+I3)*w1**2+ 1/2*I2*w2**2 + 1/2*I3*w3**2 + V0
    if opt=="precession": V = 1/2*((I3-I2)*w2*w3)**2 + 1/2*((I2)*w1*w3)**2 + 1/2*((-I3)*w1*w2)**2 + V0
    LfV=simplify(LieDerivative(F,V))
    Lg1V=simplify(LieDerivative(G1,V))
    Lg2V=simplify(LieDerivative(G2,V))
    print("L(F,V))=",latex(LfV))
    print("L(G1,V))=",latex(Lg1V))
    print("L(G2,V))=",latex(Lg2V))
    λV=lambdify((w1,w2,w3,I2,I3,I20,I30),V)
    λLfV=lambdify((w1,w2,w3,I2,I3,I20,I30),LfV)
    λLg1V=lambdify((w1,w2,w3,I2,I3,I20,I30),Lg1V)
    λLg2V=lambdify((w1,w2,w3,I2,I3,I20,I30),Lg2V)
    return λV,λLfV,λLg1V,λLg2V


def f(x,u):
    w1,w2,w3,I2,I3=x[0,0],x[1,0],x[2,0],x[3,0],x[4,0]
    I1=I2+I3
    u1,u2=u[0,0],u[1,0]
    dw1=-((I3-I2)/I1)*w2*w3-w1*(u1+u2)/I1
    dw2=-w3*w1-w2*u1/I2
    dw3= w1*w2-w3*u2/I3
    dI2=u1
    dI3=u2
    return array([[dw1],[dw2],[dw3],[dI2],[dI3]])

def control(x):
    w1,w2,w3,I2,I3=x[0,0],x[1,0],x[2,0],x[3,0],x[4,0]
    V1=     λV(w1,w2,w3,I2,I3,I20,I30)
    Lg1V=λLg1V(w1,w2,w3,I2,I3,I20,I30)
    Lg2V=λLg2V(w1,w2,w3,I2,I3,I20,I30)
    #LfV = λLfV(w1,w2,w3,I2,I3,I20,I30)
    V.append(V1)
    u=-(1/sqrt((Lg1V)**2+(Lg2V)**2))*array([[Lg1V],[Lg2V]])
    return u



def simu(E,t1,t2,colorwheel="black"):
    print("Control strategy : ",E)
    global λV,λLfV,λLg1V,λLg2V
    λV,λLfV,λLg1V,λLg2V=Symb_build(E)
    global R,x
    for t in arange(t1,t2,dt):
        I2,I3=x[3,0],x[4,0]
        I1=I2+I3
        I=diag([I1,I2,I3])
        II2.append(I2-I20)
        II3.append(I3-I30)
        wr=array([[x[0,0]],[x[1,0]],[x[2,0]]])
        #draw_wheel(R,I,wr,colorwheel)
        T.append(t)
        W1.append(wr[0,0])
        W2.append(wr[1,0])
        W3.append(wr[2,0])
        Ek.append((0.5*wr.T@I@wr)[0,0])
        Ep.append(norm(ad(I@wr)@wr))
        u=control(x)
        R=R@expw(dt*wr)
        x=array([[wr[0,0]],[wr[1,0]],[wr[2,0]],[I2],[I3]])
        x=x+dt*f(x+(dt/2)*f(x,u),u)
    return


if __name__ == "__main__":
    ax= Axes3D(figure())
    dt=0.002
    r=1
    m=10
    R=eye(3)
    #wr=R.T@w
    T,W1,W2,W3=[],[],[],[]
    Ek,Ep,Lz,V,Tr,II1,II2,II3=[],[],[],[],[],[],[],[]
    I20,I30=1/4*m*r**2,1/4*m*r**2



    opt=1    # alignment : the i vector of the well should be aligned with the angular momentum
    opt=2   # passivity based control
    opt=3   # precession"
    opt=4   # allign followed by precession"


    if opt==1:
        print("\n Aligment control \n ")
        x=array([[0.00001],[10],[0],[I20],[I30]])
        simu("align",0,40,"red")


    if opt==2:
        print("\n Passivity control \n ")
        x=array([[10],[4],[1],[I20],[I30]])
        simu("passiv",0,40,"green")

    if opt==3:
        print("\n Precession control \n ")
        x=array([[10],[4],[1],[I20],[I30]])
        simu("precession",0,80,"blue")

    if opt==4:
        print("\n Alignment controlled followed by a precession control \n ")
        x=array([[0.00001],[10],[0],[I20],[I30]])
        simu("align",0,20,"red")
        simu("precession",20,40,"blue")
        print("x=",x)