Last active
August 29, 2015 14:06
-
-
Save jaeandersson/0f93a39663be991d28a2 to your computer and use it in GitHub Desktop.
Quadcopter model by Joris Gillis
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from casadi import * | |
from casadi.tools import * | |
import numpy | |
from numpy import cos,sin, vstack, hstack, multiply | |
class Quadcopter: | |
""" | |
Quadcopter model | |
:: | |
states = [ | |
p_x \\ | |
p_y (p: centre of mass position) | |
p_z / | |
v_x \\ | |
v_y (v: centre of mass velocity) | |
v_z / | |
q_0 \\ | |
q_1 (q: orientation quaternions) | |
q_2 / | |
q_3 / | |
w_0 \\ | |
w_1 (w: angular velocity) | |
w_2 / | |
r_0 \\ | |
r_1 (r: rotor speeds) | |
r_2 / | |
r_3 / | |
] | |
controls = [ | |
CR_0 \\ | |
CR_1 (CR: motor torques) | |
CR_2 / | |
CR_3 / | |
] | |
Main usage: | |
----------- | |
from model import * | |
model = QuadcopterModel() | |
----------- | |
""" | |
def __init__(self): | |
self._model = QuadcopterModel() | |
self.x = self._model.states | |
self.dx = self._model.dstates | |
self.u = self._model.controls | |
self.r = substitute([self._model.res_], | |
[self.dx,self.x,self.u], | |
[self._model.scaling_states*SX(self.dx), | |
self._model.scaling_states*SX(self.x), | |
self._model.scaling_controls*SX(self.u) | |
])[0] | |
self.r0 = 403.60380987049399/self._model.scaling_states["r",0] | |
self.u0 = 0.00261746256/self._model.scaling_controls["CR",0] | |
self.x0 = self.x(0) | |
self.x0["p","z"] = 1 | |
self.x0["q",3] = 1 | |
self.x0["r",:] = self.r0 | |
R = jacobian(self.r,self.dx) | |
rhs = - casadi.solve(R, substitute(self.r, self.dx, 0) ) | |
self.f = SXFunction( [ self.x, self.u ], [ rhs ] ) | |
self.f.init() | |
class QuadcopterModel: | |
""" | |
Quadcopter model | |
:: | |
states = [ | |
p_x \\ | |
p_y (p: centre of mass position) | |
p_z / | |
v_x \\ | |
v_y (v: centre of mass velocity) | |
v_z / | |
q_0 \\ | |
q_1 (q: orientation quaternions) | |
q_2 / | |
q_3 / | |
w_0 \\ | |
w_1 (w: angular velocity) | |
w_2 / | |
r_0 \\ | |
r_1 (r: rotor speeds) | |
r_2 / | |
r_3 / | |
] | |
controls = [ | |
CR_0 \\ | |
CR_1 (CR: motor torques) | |
CR_2 / | |
CR_3 / | |
] | |
Main usage: | |
----------- | |
from model import * | |
model = QuadcopterModel() | |
x = model.x # states | |
dx = model.dx # state derivatives | |
u = model.u # controls | |
----------- | |
""" | |
def __init__(self,debug=False,quatnorm=False,qstab=False): | |
""" | |
Keyword arguments: | |
debug -- wether to print out debug info | |
quatnorm -- add the quaternion norm to the DAE rhs | |
""" | |
# ----------- syistem states and their derivatives ---- | |
pos = struct_symSX(["x","y","z"]) # rigid body centre of mass position [m] {0} | |
v = struct_symSX(["x","y","z"]) # rigid body centre of mass position velocity [m/s] {0} | |
NR = 4 # Number of rotors | |
states = struct_symSX([ | |
entry("p",struct=pos), | |
entry("v",struct=v), | |
entry("q",shape=4), # quaternions {0} -> {1} | |
entry("w",shape=3), # rigid body angular velocity w_101 [rad/s] {1} | |
entry("r",shape=NR) # spin speed of rotor, wrt to platform. [rad/s] Should be positive! | |
# The signs are such that positive means lift generating, regardless of spin direction. | |
]) | |
pos, v, q, w, r = states[...] | |
# ------------------------------------------------ | |
dist = struct_symSX([ | |
entry("Faer",shape=3), # Disturbance on aerodynamic forcing [N] | |
entry("Caer",shape=3) # Disturbance on aerodynamic torques [Nm] | |
]) | |
# ----------------- Controls --------------------- | |
controls = struct_symSX([ | |
entry("CR",shape=NR) # [Nm] | |
# Torques of the motors that drive the rotors, acting from platform on propeller | |
# The torque signs are always positive when putting energy in the propellor, | |
# regardless of spin direction. | |
# | |
]) | |
CR = controls["CR"] | |
# ------------------------------------------------ | |
# ---------------- Temporary symbols -------------- | |
F = SX.sym("F",3) # Forces acting on the platform in {1} [N] | |
C = SX.sym("C",3) # Torques acting on the platform in {1} [Nm] | |
rotors_Faer = [SX.sym("Faer_%d" %i,3,1) for i in range(NR)] # Placeholder for aerodynamic force acting on propeller {1} [N] | |
rotors_Caer = [SX.sym("Caer_%d" %i,3,1) for i in range(NR)] # Placeholder for aerodynamic torques acting on propeller {1} [Nm] | |
# --------------------------------------------------- | |
# ----------------- Parameters --------------------- | |
rotor_model = struct_symSX([ | |
"c", # c Cord length [m] | |
"R", # R Radius of propeller [m] | |
"CL_alpha", # CL_alpha Lift coefficient [-] | |
"alpha_0", # alpha_0 | |
"CD_alpha", # CD_alpha Drag coefficient [-] | |
"CD_i", # CD_i Induced drag coefficient [-] | |
]) | |
p = struct_symSX([ | |
entry("rotors_model",repeat=NR,struct=rotor_model), # Parameters that describe the rotor model | |
entry("rotors_I",repeat=NR,shape=Sparsity.diag(3)), # Inertias of rotors [kg.m^2] | |
entry("rotors_spin",repeat=NR), # Direction of spin from each rotor. 1 means rotation around positive z. | |
entry("rotors_p",repeat=NR,shape=3), # position of rotors in {1} [m], | |
entry("I",sym=diag(SX.sym("[Ix,Iy,Iz]"))), # Inertia of rigid body [kg.m^2] | |
"m", # Mass of the whole system [kg] | |
"g", # gravity [m/s^2] | |
"rho", # Air density [kg/m^3] | |
]) | |
I,m,g,rho = p[["I","m","g","rho"]] | |
# -------------------------------------------------- | |
# ----------------- Parameters fillin's --------------------- | |
p_ = p() | |
p_["rotors_spin"] = [1,-1,1,-1] | |
p_["rotors_model",:,{}] = { "c": 0.01, "R" : 0.127, "CL_alpha": 6.0, "alpha_0": 0.15, "CD_alpha": 0.02, "CD_i": 0.05} # c Cord length [m] | |
p_["m"] = 0.5 # [kg] | |
p_["g"] = 9.81 # [N/kg] | |
p_["rho"] = 1.225 # [kg/m^3] | |
L = 0.25 | |
I_max = p_["m"] * L**2 # Inertia of a point mass at a distance L | |
I_ref = I_max/5 | |
#print "q I_max", I_max | |
#print "q I_ref", I_ref | |
#print "q I_ref/2", I_ref/2 | |
p_["I"] = diag([I_ref/2,I_ref/2,I_ref]) # [N.m^2] | |
p_["rotors_p",0] = DMatrix([L,0,0]) | |
p_["rotors_p",1] = DMatrix([0,L,0]) | |
p_["rotors_p",2] = DMatrix([-L,0,0]) | |
p_["rotors_p",3] = DMatrix([0,-L,0]) | |
for i in range(NR): | |
R_ = p_["rotors_model",i,"R"] # Radius of propeller [m] | |
m_ = 0.01 # Mass of a propeller [kg] | |
I_max = m_ * R_**2 # Inertia of a point mass | |
#print "rotor I_max", I_max | |
I_ref = I_max/5 | |
#print "rotor I_ref", I_ref | |
#print "rotor I_ref/2", I_ref/2 | |
p_["rotors_I",i] = diag([I_ref/2,I_ref/2,I_ref]) | |
if debug: | |
print p.vecNZcat() | |
dist_ = dist(0) | |
# ----------------- Scaling --------------------- | |
scaling_states = states(1) | |
scaling_controls = controls(1) | |
scaling_states["r"] = 500 | |
scaling_controls["CR"] = 0.005 | |
scaling_dist = dist() | |
scaling_dist["Faer"] = float(p_["m"]*p_["g"]/NR) | |
scaling_dist["Caer"] = 0.0026 | |
# ----------- Frames ------------------ | |
T_10 = mul(tr(*pos),Tquat(*q)) | |
T_01 = kin_inv(T_10) | |
R_10 = T2R(T_10) | |
R_01 = R_10.T | |
# ------------------------------------- | |
dstates = struct_symSX(states) | |
dp,dv,dq,dw,dr = dstates[...] | |
res = struct_SX(states) # DAE residual hand side | |
# ----------- Dynamics of the body ---- | |
res["p"] = v - dp | |
# Newton, but transform the force F from {1} to {0} | |
res["v"] = mul(R_10,F) - m*dv | |
# Kinematics of the quaterion. | |
res["q"] = mul(quatDynamics(*q),w)-dq | |
#print mul(quatDynamics(*q),w) | |
#Skew = jacobian(mul(quatDynamics(*q),w),q) | |
#print "skew=",Skew | |
#print "skew2=",mul(jacobian(quatDynamics(*q),q),w).reshape((4,4)) | |
#d = mul([q.T,Skew,q]) | |
#a = DMatrix([1,2,0.3,0.7]) | |
#a=a/sqrt(sumAll(a**2)) | |
#print jacobian(substitute(d,q,a),w) | |
# This is a trick by Sebastien Gros to stabilize the quaternion evolution equation | |
if qstab: | |
res["q"] += -q*(sumAll(q**2)-1) | |
# Agular impulse H_1011 | |
H = mul(p["I"],w) # Due to platform | |
for i in range(NR): | |
H+= mul(p["rotors_I",i], w + vertcat([0,0,p["rotors_spin",i]*r[i]])) # Due to rotor i | |
self.H = H | |
dH = mul(jacobian(H,w),dw) + mul(jacobian(H,q),dq) + mul(jacobian(H,r),dr) + cross(w,H) | |
# dH = mul(jacobian(H,vertcat([w,q,r])),vertcat([dw,dq,dr])) + cross(w,H) #same | |
# More expensive.. | |
#temp = SXFunction([vertcat([w,q,r])],[H]) | |
#temp.init() | |
#d = temp.derivative(1,0) | |
#d.init() | |
#dH = d([vertcat([w,q,r]),vertcat([dw,dq,dr])])[1]+ cross(w,H) | |
#dotdraw(dH) | |
res["w"] = C - dH | |
for i in range(NR): | |
res["r",i] = CR[i] - rotors_Caer[i][2] - p["rotors_I",i][2,2]*(dr[i]+p["rotors_spin",i]*dw[2]) # Dynamics of rotor i | |
# --------------------------------- | |
# Make a vector of f ? | |
#if quatnorm: | |
# f = vertcat(f+[sumAll(q**2)-1]) | |
#else: | |
# f = vertcat(f) | |
# ------------ Force model ------------ | |
Fg = mul(R_01,vertcat([0,0,-g*m])) | |
F_total = Fg + sum(rotors_Faer) + dist["Faer"] # Total force acting on the platform | |
C_total = SX([0,0,0]) + dist["Caer"] # Total torque acting on the platform | |
for i in range(NR): | |
C_total[:2] -= p["rotors_spin",i]*rotors_Caer[i][:2] # The x and y components propagate | |
# There are no components.. | |
# The rotor inertia arising from I*dw[2] is already present in the platform dynamic balance | |
#C_total[2] -= p["rotors_spin",i]*CR[i]-p["rotors_I",i][2,2]*dw[2] # the z compent moves through a serparate system | |
C_total[2]-= p["rotors_spin",i]*rotors_Caer[i][2] | |
C_total += cross(p["rotors_p",i],rotors_Faer[i]) # Torques due to thrust | |
res = substitute(res,F,F_total) | |
res = substitute(res,C,C_total) | |
subs_before = [] | |
subs_after = [] | |
v_global = mul(R_01,v) | |
u_z = SX([0,0,1]) | |
# Now fill in the aerodynamic forces | |
for i in range(NR): | |
c,R,CL_alpha,alpha_0, CD_alpha, CD_i = p["rotors_model",i,...] | |
#Bristeau P-jean, Martin P, Salaun E, Petit N. The role of propeller aerodynamics in the model of a quadrotor UAV. In: Proceedings of the European Control Conference 2009.; 2009:683-688. | |
v_local = v_global + (cross(w,p["rotors_p",i])) # Velocity at rotor i | |
rotors_Faer_physics = (rho*c*R**3*r[i]**2*CL_alpha*(alpha_0/3.0-v_local[2]/(2.0*R*r[i]))) * u_z | |
subs_before.append(rotors_Faer[i]) | |
subs_after.append(rotors_Faer_physics) | |
rotors_Caer_physics = rho*c*R**4*r[i]**2*(CD_alpha/4.0+CD_i*alpha_0**2*(alpha_0/4.0-2.0*v_local[2]/(3.0*r[i]*R))-CL_alpha*v_local[2]/(r[i]*R)*(alpha_0/3.0-v_local[2]/(2.0*r[i]*R))) * u_z | |
subs_before.append(rotors_Caer[i]) | |
subs_after.append(rotors_Caer_physics ) | |
res = substitute(res,veccat(subs_before),veccat(subs_after)) | |
# Make an explicit ode | |
rhs = - solve(jacobian(res,dstates),substitute(res,dstates,0)) | |
# -------------------------------------- | |
self.res_w = res | |
self.res = substitute(res,dist,dist_) | |
self.res_ = substitute(self.res,p,p_) | |
resf = SXFunction([dstates, states, controls ],[self.res_]) | |
resf.setOption("name","resf") | |
resf.init() | |
self.resf = resf | |
self.rhs_w = rhs | |
self.rhs = substitute(rhs,dist,dist_) | |
self.rhs_ = substitute(self.rhs,p,p_) | |
self.H = substitute(substitute(H,p,p_),dist,dist_) | |
t = SX.sym("t") | |
# We end up with a DAE that captures the system dynamics | |
dae = SXFunction(daeIn(t=t,x=states,p=controls),daeOut(ode=self.rhs_)) | |
dae.setOption("name","dae") | |
dae.init() | |
self.dae = dae | |
cdae = SXFunction(controldaeIn(t=t, x=states, u= controls,p=p),daeOut(ode=self.rhs)) | |
cdae.setOption("name","cdae") | |
cdae.init() | |
self.cdae = cdae | |
self.states = states | |
self.dstates = dstates | |
self.p = p | |
self.p_ = p_ | |
self.controls = controls | |
self.NR = NR | |
self.w = dist | |
self.w_ = dist_ | |
self.t = t | |
self.states_ = states() | |
self.dstates_ = states() | |
self.controls_ = controls() | |
self.scaling_states = scaling_states | |
self.scaling_controls = scaling_controls | |
self.scaling_dist = scaling_dist | |
casadiAvailable = False | |
casadiTypes = set() | |
try: | |
import casadi as c | |
casadiAvailable = True | |
casadiTypes = set([type(c.SXElement()),type(c.SX())]) | |
except ImportError: | |
pass | |
def TRx(a): | |
constr = numpy.matrix | |
if casadiAvailable and type(a) in casadiTypes: | |
constr = c.blockcat | |
return constr([[1,0,0,0],[0,cos(a),-sin(a),0],[0,sin(a),cos(a),0],[0,0,0,1]]) | |
def TRy(a): | |
constr = numpy.matrix | |
if casadiAvailable and type(a) in casadiTypes: | |
constr = c.blockcat | |
return constr([[cos(a),0,sin(a),0],[0,1,0,0],[-sin(a),0,cos(a),0],[0,0,0,1]]) | |
def TRz(a): | |
constr = numpy.matrix | |
if casadiAvailable and type(a) in casadiTypes: | |
constr = c.blockcat | |
return constr([[cos(a),-sin(a),0,0],[sin(a),cos(a),0,0],[0,0,1,0],[0,0,0,1]]) | |
def tr(x,y,z): | |
return c.blockcat([[1,0,0,x],[0,1,0,y],[0,0,1,z],[0,0,0,1]]) | |
def scale(a): | |
return numpy.matrix([[a,0,0],[0,a,0],[0,0,a]]) | |
def Tscale(a): | |
return R2T(scale(a)) | |
def Tquat(q0,q1,q2,q3): | |
return R2T(quat(q0,q1,q2,q3)) | |
def quat(q0,q1,q2,q3): | |
""" | |
From Jeroen's presentation. q = [e*sin(theta/2); cos(theta/2)] | |
""" | |
constr = c.blockcat | |
types = set([type(q) for q in [q0,q1,q2,q3]]) | |
#if not(types.isdisjoint(casadiTypes)): | |
# constr = c.blockcat | |
rho = constr([[q0],[q1],[q2]]) | |
rho_skew = skew(rho) | |
I_3 = constr([[1.0,0,0],[0,1.0,0],[0,0,1.0]]) | |
#A = multiply(I_3,(numpy.dot(rho.T,-rho)+q3*q3))+numpy.dot(rho,rho.T)*2.0-q3*rho_skew*2.0 | |
b = q0 | |
c_ = q1 | |
d = q2 | |
a = q3 | |
a2 = a**2 | |
b2 = b**2 | |
c2 = c_**2 | |
d2 = d**2 | |
am2 = -a2 | |
bm2 = -b2 | |
cm2 = -c2 | |
dm2 = -d2 | |
bb = 2*b | |
aa = 2*a | |
bc2 = bb*c_ | |
bd2 = bb*d | |
ac2 = aa*c_ | |
ab2 = aa*b | |
ad2 = aa*d | |
cd2 = 2*c_*d | |
A = constr([[a2+b2+cm2+dm2, bc2 - ad2, bd2 + ac2],[bc2 + ad2, a2+bm2+c2+dm2, cd2 - ab2], [ bd2 -ac2, cd2 + ab2, a2+bm2+cm2+d2]]).T | |
return A.T | |
def quatOld(q0,q1,q2,q3): | |
""" | |
From Shabana AA. Dynamics of multibody systems. Cambridge Univ Pr; 2005. | |
defined as [ cos(theta/2) e*sin(theta/2) ] | |
""" | |
constr = numpy.matrix | |
types = set([type(q) for q in [q0,q1,q2,q3]]) | |
#if not(types.isdisjoint(casadiTypes)): | |
# constr = c.blockcat | |
E = constr([[-q1, q0, -q3, q2],[-q2, q3, q0, -q1],[-q3,-q2,q1,q0]]) | |
Eb = constr([[-q1, q0, q3, -q2],[-q2, -q3, q0, q1],[-q3,q2,-q1,q0]]) | |
if not(types.isdisjoint(casadiTypes)): | |
constr = c.blockcat | |
return constr(numpy.dot(E,Eb.T)) | |
def fullR(R_0_0,R_1_0,R_2_0, R_0_1, R_1_1, R_2_1, R_0_2, R_1_2, R_2_2): | |
constr = numpy.matrix | |
types = set([type(q) for q in [R_0_0,R_1_0,R_2_0, R_0_1, R_1_1, R_2_1, R_0_2, R_1_2, R_2_2]]) | |
if not(types.isdisjoint(casadiTypes)): | |
constr = c.blockcat | |
return constr([[R_0_0, R_0_1, R_0_2],[R_1_0, R_1_1, R_1_2 ],[R_2_0, R_2_1, R_2_2 ]]) | |
def TfullR(R_0_0,R_1_0,R_2_0, R_0_1, R_1_1, R_2_1, R_0_2, R_1_2, R_2_2): | |
return R2T(fullR(R_0_0,R_1_0,R_2_0, R_0_1, R_1_1, R_2_1, R_0_2, R_1_2, R_2_2)) | |
def origin() : | |
return tr(0,0,0) | |
def trp(T): | |
return numpy.matrix(T)[:3,3] | |
def kin_inv(T): | |
R=numpy.matrix(T2R(T).T) | |
constr = numpy.matrix | |
if type(T) in casadiTypes: | |
constr = c.blockcat | |
return constr(vstack((hstack((R,-numpy.dot(R,trp(T)))),numpy.matrix([0,0,0,1])))) | |
def vectorize(vec): | |
""" | |
Make sure the result is something you can index with single index | |
""" | |
if hasattr(vec,"shape"): | |
if vec.shape[0] > 1 and vec.shape[1] > 1: | |
raise Exception("vectorize: got real matrix instead of vector like thing: %s" % str(vec)) | |
if vec.shape[1] > 1: | |
vec = vec.T | |
if hasattr(vec,"tolist"): | |
vec = [ i[0] for i in vec.tolist()] | |
return vec | |
def skew(vec): | |
myvec = vectorize(vec) | |
x = myvec[0] | |
y = myvec[1] | |
z = myvec[2] | |
constr = numpy.matrix | |
types = set([type(q) for q in [x,y,z]]) | |
if not(types.isdisjoint(casadiTypes)): | |
constr = c.blockcat | |
return constr([[0,-z,y],[z,0,-x],[-y,x,0]]) | |
def invskew(S): | |
return c.SX([S[2,1],S[0,2],S[1,0]]) | |
def cross(a,b): | |
return c.mul(skew(a),b) | |
def T2R(T): | |
""" | |
Rotational part of transformation matrix | |
""" | |
return T[0:3,0:3] | |
def R2T(R): | |
""" | |
Pack a rotational matrix in a homogenous form | |
""" | |
T = c.SX([[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,1.0]]) | |
T[:3,:3] = R | |
return T | |
def T2w(): | |
""" | |
skew(w_100) = T2w(T_10) | |
""" | |
def T2W(T,p,dp): | |
""" | |
w_101 = T2W(T_10,p,dp) | |
""" | |
R = T2R(T) | |
dR = c.reshape(c.mul(c.jacobian(R,p),dp),(3,3)) | |
return invskew(c.mul(R.T,dR)) | |
def quatDynamics(q0,q1,q2,q3): | |
""" | |
dot(q) = quatDynamics(q)*w_101 | |
""" | |
constr = numpy.matrix | |
if type(q0) in casadiTypes: | |
constr = c.blockcat | |
B = constr([[q3,-q2,q1],[q2,q3,-q0],[-q1,q0,q3],[-q0,-q1,-q2]])*0.5 | |
return B | |
def T2WJ(T,p): | |
""" | |
w_101 = T2WJ(T_10,p).diff(p,t) | |
""" | |
R = T2R(T) | |
RT = R.T | |
temp = [] | |
for i,k in [(2,1),(0,2),(1,0)]: | |
#temp.append(c.mul(c.jacobian(R[:,k],p).T,R[:,i]).T) | |
temp.append(c.mul(RT[i,:],c.jacobian(R[:,k],p))) | |
return c.vertcat(temp) | |
def evalModel(model): | |
states = model.states | |
dstates = model.dstates | |
controls = model.controls | |
p = model.p | |
states_ = model.states_ | |
dstates_ = model.dstates_ | |
controls_ = model.controls_ | |
p_ = model.p_ | |
rhs = model.rhs | |
res = model.res | |
# We end up with a DAE that captures the system dynamics | |
dyn = SXFunction([dstates, states, controls ],[res]) | |
dyn.setOption("name","dyn") | |
dyn.init() | |
NR = model.NR | |
resd = dyn([SX(dstates_), SX(states_), SX(controls_)])[0] | |
res = dyn([SX(dstates_), SX(states_), SX(controls_)])[0] | |
res_ = substitute(res,p,p_) | |
for v in zip(states.cat,resd,res,res_): | |
print v[0] | |
for i in v[1:]: | |
print " ", i | |
return (res,res_) | |
def unittests(): | |
model = QuadcopterModel() | |
states = model.states | |
dstates = model.dstates | |
controls = model.controls | |
p = model.p | |
states_ = model.states_ | |
dstates_ = model.dstates_ | |
controls_ = model.controls_ | |
p_ = model.p_ | |
NR = model.NR | |
# ----------------- do quick checks on the model ----- | |
print "zero input -> gravity" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [0,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print res[i] | |
if i == states.f["v"][2]: | |
print res_[i].toScalar() | |
assert(res_[i].toScalar().getValue()==-p_["g"]*p_["m"]) | |
else: | |
assert(res_[i].toScalar().getValue()==0) | |
print "platform rotating around +z" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [0,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,1] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print res[i] | |
if i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()==-p_["g"]*p_["m"]) | |
elif i == states.f["q"][2]: | |
assert(res[i].toScalar().isEqual(0.5)) # The quaternion is changing | |
else: | |
assert(res_[i].toScalar().getValue()==0) | |
print "platform accelerates around +z" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [0,0,0,0] | |
dstates_["r"] = [-1,1,-1,1] # The rotors keep steady in the inertial frame | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,0] | |
dstates_["w"] = [0,0,1] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print res[i] | |
if i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()==-p_["g"]*p_["m"]) | |
elif i in list(states.f["r"]): | |
assert(res_[i].toScalar().getValue()==0) # since the rotors are mounted in vertical bearing | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()<0) | |
else: | |
assert(res_[i].toScalar().getValue()==0) | |
print "One rotor works" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print states.cat[i],res[i], res_[i].toScalar() | |
if i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()>-p_["g"]) # gravity pulls down | |
elif i == states.f["w"][1]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -y | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -z | |
elif i == states.f["r"][0]: | |
assert(res_[i].toScalar().getValue()<0) # rotor slows down | |
else: | |
assert(res_[i].toScalar().isEqual(0)) | |
thrust = res_[states.f["v"][2]].toScalar().getValue()+p_["g"] | |
angular = res_[states.f["w"][1]].toScalar().getValue() | |
slowdown = res_[states.f["r"][0]].toScalar().getValue() | |
print "One rotor works while platform moves upwards" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0.01] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print states.cat[i], res[i], res_[i].toScalar() | |
if i == states.f["p"][2]: | |
assert(res_[i].toScalar().getValue()==states_["v"][2]) # dx = v | |
elif i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()+p_["g"]<thrust) # Translation speed makes for decreased propeller lift | |
elif i == states.f["w"][1]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -y | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -z | |
elif i == states.f["r"][0]: | |
assert(abs(res_[i].toScalar().getValue())<abs(slowdown)) # rotor slows down less than without the upward movement, because lift is smaller | |
else: | |
assert(res_[i].toScalar().isEqual(0)) | |
print "One rotor works while platform moves sideways" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [1,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print res[i], res_[i].toScalar(),abs(slowdown) | |
if i == states.f["p"][0]: | |
assert(res_[i].toScalar().getValue()==states_["v"][0]) # dx = v | |
elif i == states.f["v"][2]: | |
print "debug:" , res_[i].toScalar().getValue()+p_["g"], thrust | |
assert(res_[i].toScalar().getValue()+p_["g"]<=thrust) # Translation speed makes for decreased propeller lift | |
elif i == states.f["w"][1]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -y | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -z | |
elif i == states.f["r"][0]: | |
assert(abs(res_[i].toScalar().getValue())==abs(slowdown)) # rotor slows down less, because lift is smaller | |
else: | |
assert(res_[i].toScalar().isEqual(0)) | |
print "One rotor works while platform rotates around positive local x-axis" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0.05,0,0,0] | |
states_["w"] = [0.1,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print res[i], res_[i].toScalar(),abs(thrust) | |
if i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()+p_["g"]==thrust) # No change | |
elif i == states.f["w"][1]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -y + some gyro effects | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -z | |
elif i == states.f["r"][0]: | |
assert(res_[i].toScalar().getValue()==slowdown) # No change | |
else: | |
assert(res_[i].toScalar().isEqual(0)) | |
print "One rotor works while platform rotates around axis through rotor center and parallel to y-axis (positive)" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0.25*0.1] #L*omega | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,0.05,0,0] | |
states_["w"] = [0,0.1,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print res[i], res_[i].toScalar() | |
if i == states.f["p"][2]: | |
assert(res_[i].toScalar().getValue()==states_["v"][2]) | |
elif i == states.f["w"][0]: | |
assert(res_[i].toScalar().getValue()<0) # Gyroscopic effect: accelerarion around -x | |
elif i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()+p_["g"]==thrust) # No change | |
elif i == states.f["w"][1]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -y | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -z | |
elif i == states.f["r"][0]: | |
assert(res_[i].toScalar().getValue()==slowdown) # rotor slows down less, because lift is smaller | |
else: | |
assert(res_[i].toScalar().isEqual(0)) | |
print "One rotor works while platform is tilted 45 degrees along -y" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,-sin(pi/8),0,cos(pi/8)] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print states.cat[i], res[i], res_[i].toScalar() | |
if i == states.f["v"][0]: | |
assert(res_[i].toScalar().getValue()<0) # Thrust in -x direction | |
elif i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()+p_["g"]<thrust) # Inclination makes for smaller upward force | |
elif i == states.f["w"][1]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -y | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -z | |
elif i == states.f["r"][0]: | |
assert(res_[i].toScalar().getValue()<0) # rotor slows down | |
else: | |
assert(res_[i].toScalar().isEqual(0)) | |
print "One rotor works while platform is tilted 45 degrees along +y" | |
# @TODO: make this unittest | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,sin(pi/8),0,cos(pi/8)] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
if i == states.f["v"][0]: | |
assert(res_[i].toScalar().getValue()>0) # Thrust in -x direction | |
elif i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()+p_["g"]<thrust) # Inclination makes for smaller upward force | |
elif i == states.f["w"][1]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -y | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -z | |
elif i == states.f["r"][0]: | |
assert(res_[i].toScalar().getValue()<0) # rotor slows down | |
else: | |
assert(res_[i].toScalar().isEqual(0)) | |
print "platform rotating around +x" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [0,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0.5,0,0,0] | |
states_["w"] = [1,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print res[i] | |
if i == states.f["v"][2]: | |
assert(res[i].toScalar().isEqual((-p["g"]*p["m"]).at(0),2)) | |
else: | |
assert(res[i].toScalar().isEqual(0)) | |
print "platform rotating around -y with one rotor spinning" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,-0.05,0,0] # The quaternion is changing | |
states_["w"] = [0,-0.1,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print res[i], res_[i].toScalar() | |
if i == states.f["v"][2]: | |
assert(res_[i].toScalar()+p_["g"]*p_["m"]<thrust) # Decreased lift due to local velocity at the rotor | |
elif i == states.f["q"][1]: | |
assert(res_[i].toScalar().getValue()==0) | |
elif i == states.f["w"][0]: | |
assert(res_[i].toScalar().getValue()>0) # Gyroscopic effect: accelerarion around +x | |
elif i == states.f["w"][1]: | |
assert(res_[i].toScalar().getValue()<0) # Acceleration around - y due to rotor thrust | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -z | |
elif i == states.f["r"][0]: | |
assert(res_[i].toScalar().getValue()<0) # rotor slows down | |
else: | |
assert(res[i].toScalar().isEqual(0)) | |
print "platform rotating around -y with all rotors spinning" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,100,100,100] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,-0.005,0,0] # The quaternion is changing | |
states_["w"] = [0,-0.01,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
print res[i], res_[i].toScalar().getValue(), angular | |
if i == states.f["v"][2]: | |
assert(res_[i].toScalar()>-p_["g"]*p_["m"]) | |
elif i == states.f["w"][0]: | |
assert(res_[i].toScalar().getValue()==0) # Gyroscopic effects cancel out | |
#elif i == states.f["w"][1]: | |
# assert(res_[i].toScalar().getValue()==0) # Thrusts cancel out # improved aero: thrusts do not cancel exactly | |
elif i == states.f["w"][1]: | |
assert(abs(res_[i].toScalar().getValue())<=0.2*abs(angular)) # improved aero: thrusts do not cancel exactly, let's say they must cancel up to 20% | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -z: torques do not cancel out completely | |
elif i == states.f["q"][1]: | |
assert(res_[i].toScalar().getValue()==0) | |
elif i in states.f["r"]: | |
assert(res_[i].toScalar().getValue()<0) # rotor slows down | |
else: | |
assert(res[i].toScalar().isEqual(0)) | |
print "One rotor with positive spin accelerates" | |
controls_["CR"]= [1,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,0,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
if i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()>-p_["g"]) # gravity pulls down | |
elif i == states.f["w"][1]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -y | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()<0) # platform rotates over -z | |
elif i == states.f["r"][0]: | |
print p_["rotors_I",0,2,2] | |
assert(res_[i].toScalar().getValue()<controls_["CR"][0]/p_["rotors_I",0,2,2]) | |
# Rotor accelerates, but less than in vacuum | |
else: | |
assert(res_[i].toScalar().isEqual(0)) | |
print "One rotor with negative spin accelerates" | |
controls_["CR"]= [0,1,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [0,100,0,0] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
if i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()>-p_["g"]) # gravity pulls down | |
elif i == states.f["w"][0]: | |
assert(res_[i].toScalar().getValue()>0) # platform rotates over +x | |
elif i == states.f["w"][2]: | |
assert(res_[i].toScalar().getValue()>0) # platform rotates over +z | |
elif i == states.f["r"][1]: | |
assert(res_[i].toScalar().getValue()<controls_["CR"][1]/p_["rotors_I",1,2,2]) | |
# Rotor accelerates, but less than in vacuum | |
else: | |
assert(res_[i].toScalar().isEqual(0)) | |
print "All rotors rotate constant" | |
controls_["CR"]= [0,0,0,0] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,100,100,100] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
if i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()>-p_["g"]) # gravity pulls down | |
elif i in states.f["r"]: | |
assert(res_[i].toScalar().getValue()<0) # all rotors slow down | |
else: | |
assert(res_[i].toScalar().isEqual(0)) | |
print "All rotors accelerate" | |
controls_["CR"]= [1,1,1,1] | |
states_["p"] = [0,0,0] | |
dstates_["p"] = [0,0,0] | |
states_["v"] = [0,0,0] | |
dstates_["v"] = [0,0,0] | |
states_["r"] = [100,100,100,100] | |
dstates_["r"] = [0,0,0,0] | |
states_["q"] = [0,0,0,1] | |
dstates_["q"] = [0,0,0,0] | |
states_["w"] = [0,0,0] | |
dstates_["w"] = [0,0,0] | |
[res, res_] = evalModel(model) | |
for i in range(res.size()): | |
if i == states.f["v"][2]: | |
assert(res_[i].toScalar().getValue()>-p_["g"]) # gravity pulls down | |
elif i in states.f["r"]: | |
assert(res_[i].toScalar().getValue()<controls_["CR",int(i-states.f["r"][0])]/p_["rotors_I",int(i-states.f["r"][0]),2,2]) | |
# Rotor accelerates, but less than in vacuum | |
else: | |
assert(res_[i].toScalar().isEqual(0)) | |
if __name__ == '__main__': | |
unittests() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from casadi import * | |
from quadcopter import * | |
from casadi.tools import * | |
from pylab import * | |
show_3d = False | |
# Construct the quadcopter model | |
model = Quadcopter() | |
f = model.f | |
# Construct the explicit ODE | |
R = jacobian(model.r,model.dx) | |
rhs = - casadi.solve(R, substitute(model.r, model.dx, 0) ) | |
f = SXFunction( [ model.x, model.u ], [ rhs ] ) | |
f.init() | |
T = 3.0 | |
N = 200 | |
DT = T/N | |
# This constructs an object that behaves like an MX, | |
# but has convenient accessors | |
# e.g W["X",0] returns the state at the first control interval | |
# | |
# For more information about this CasADi feature, | |
# you could consult an online tutorial | |
# docs.casadi.org -> scroll to bottom | |
# -> click tutorials | |
# -> tools | |
# -> structure.pdf | |
W = struct_symMX([ | |
( | |
entry("X",struct=model.x,repeat=N+1), | |
entry("Y",struct=model.x,repeat=N), | |
entry("U",struct=model.u,repeat=N) | |
) | |
]) | |
ts = [0] | |
t = 0 | |
# Build up the list of constraints | |
g = [] | |
for i in range(N): | |
# Collocation equations | |
C = [-2,2] | |
xp_j = C[0]*W["X",i] + C[1]*W["Y",i] | |
[f_j] = f([ W["Y",i], W["U",i] ]) | |
g.append(DT*f_j - xp_j) | |
# Continuity equations | |
D = [-1,2] | |
xf = D[0]*W["X",i] + D[1]*W["Y",i] | |
g.append(xf - W["X",i+1]) | |
t = t + DT | |
ts.append(t) | |
g = vertcat(g) | |
# Construct the reference to track | |
traj = [ array([sin(i*DT*2*pi/3),i*DT*2*pi/3,0]) for i in range(N) ] | |
# Construct the objective | |
R1 = vertcat([ p-t for p,t in zip(W["X",:,"p"],traj) ]) | |
R2 = vertcat(W["U",:]) | |
alpha = 0.05 | |
f = (T/N) * ( mul(R1.T, R1) + alpha*mul(R2.T, R2) ) | |
# Create the NLP | |
nlp = MXFunction( nlpIn(x=W), nlpOut(f=f, g=g) ) | |
nlp.init() | |
# Create an IPOPT NLP solver | |
solver = NlpSolver("ipopt",nlp) | |
solver.setOption("linear_solver","mumps") # todo: remove | |
# If we need more than 100 iterations, something is wrong | |
solver.setOption("max_iter",100) | |
solver.init() | |
# All constraints are equality constraints in this case | |
solver.setInput(0, "lbg") | |
solver.setInput(0, "ubg") | |
# Construct and populate the vectors with | |
# upper and lower simple bounds | |
# | |
# lbx is an array in disguise. | |
# You can view the underlying array as lbx.cat | |
lbx = W(-inf) | |
ubx = W(inf) | |
# 0 <= u_k <= 0.5 | |
lbx["U",:] = 0 | |
ubx["U",:] = 0.5 | |
# p_0 = [0,0,0]^T | |
lbx["X",0,"p"] = 0.0 | |
ubx["X",0,"p"] = 0.0 | |
# v_0 = [0,0,0]^T | |
lbx["X",0,"v"] = 0.0 | |
ubx["X",0,"v"] = 0.0 | |
# Construct a vector with the initial guess | |
x0 = W(0) | |
x0["X",:] = model.x0 | |
x0["Y",:] = model.x0 | |
x0["U",:] = model.u0 | |
solver.setInput(x0, "x0") | |
solver.setInput(lbx, "lbx") | |
solver.setInput(ubx, "ubx") | |
# Solve the NLP | |
solver.evaluate() | |
# Cast the result vector in a form | |
# that we can easily access | |
sol = W(solver.getOutput("x")) | |
# Save solution to a file | |
import pickle | |
pickle.dump(sol,file('tracking.pkl','w')) | |
X = sol["X",:,"p","x"] | |
Y = sol["X",:,"p","y"] | |
Z = sol["X",:,"p","z"] | |
# 2D plots | |
figure() | |
plot(ts,X,label="p_x") | |
plot(ts,Y,label="p_y") | |
plot(ts,Z,label="p_z") | |
plot(ts[:-1],horzcat(traj)[0,:].T,label="p_ref_x") | |
plot(ts[:-1],horzcat(traj)[1,:].T,label="p_ref_y") | |
xlabel("Time [s]") | |
legend(loc="upper left") | |
title("State trajectories") | |
figure() | |
plot(X,Y,'.',label="optimized") | |
plot(horzcat(traj)[0,:].T,horzcat(traj)[1,:].T,'.',label="reference") | |
legend() | |
title("Top down trajectory view") | |
xlabel("x [m]") | |
xlabel("y [m]") | |
axis('equal') | |
figure() | |
step(ts, horzcat(sol["U",:]+[ sol["U",-1] ]).T,where='post') | |
xlabel("Time [s]") | |
title("Control trajectories") | |
ylim([-0.1,0.6]) | |
if show_3d: | |
# 3D plots | |
from mpl_toolkits.mplot3d import Axes3D | |
figure() | |
ax = gca(projection='3d') | |
ax.plot(array(X),array(Y),array(Z),"b.",label="optimized") | |
Traj = array(horzcat(traj)) | |
ax.plot(Traj[:,0],Traj[:,1],Traj[:,2],'g.',label="reference") | |
# Plot the rotors | |
circle = array([ [cos(t),sin(t),0] for t in linspace(0,2*pi) ]).T*0.1 | |
for p, q in zip(sol["X",::20,"p"],sol["X",::20,"q"]): | |
for offset in [ array([[1,0,0]]),array([[0,1,0]]),array([[-1,0,0]]),array([[0,-1,0]]) ]: | |
circle_offset = circle + 0.1*offset.T | |
circle_3D = mul(quat(*q), circle_offset) | |
ax.plot( | |
array(p[0]+circle_3D[0,:]).squeeze(), | |
array(p[1]+circle_3D[1,:]).squeeze(), | |
array(p[2]+circle_3D[2,:]).squeeze(), | |
'k' | |
) | |
ax.set_xlim([-pi,pi]) | |
ax.set_ylim([0,2*pi]) | |
ax.set_zlim([-pi,pi]) | |
legend() | |
show() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from quadcopter import * | |
from casadi.tools import * | |
from pylab import * | |
show_3d = False | |
"""" | |
This file solves a time-optimal problem | |
min T + alpha*int || u(t) ||_2^2 + || w(t) ||_2^2 + || q(t) -q^ref ||_2^2 dt | |
x(t), u(t) | |
subject to || p(t)-pA ||^2 >= 0.8 obstacle avoidance | |
|| p(t)-pB ||^2 >= 0.8 obstacle avoidance | |
0 <= u <= 0.5 | |
p_0 = [0,0,0]^T | |
v_0 = [0,0,0]^T | |
p_N = [0,2*pi,0]^T | |
v_N_y = 0 | |
v_N_z = 0 | |
alpha = 0.025 | |
""" | |
# Initialize with the solution of the tracking problem | |
pA = np.array([0, pi/2, 0]) | |
pB = np.array([0, 3*pi/2, 0]) | |
ps = [pA, pB] | |
rs = [0.8, 0.8] | |
# Import the tracking solution (for initialisation) | |
import pickle | |
sol_tracking = pickle.load(file('tracking.pkl','r')) | |
# Construct the quadcopter model | |
model = Quadcopter() | |
f = model.f | |
N = 200 | |
# This constructs an object that behaves like an MX, | |
# but has convenient accessors | |
W = struct_symMX([ | |
( | |
entry("X",struct=model.x,repeat=N+1), | |
entry("Y",struct=model.x,repeat=N), | |
entry("U",struct=model.u,repeat=N) | |
), | |
entry("T") | |
]) | |
T = W["T"] | |
DT = T/N | |
ts = [0] | |
t = 0 | |
# Build up the list of constraints | |
g = [] | |
for i in range(N): | |
[xdot] = f([ W["Y",i], W["U",i] ]) | |
g.append(...) | |
g.append(...) | |
t = t + DT | |
ts.append(t) | |
def norm22(x): | |
return mul(x.T,x) | |
# This time, we have both equalities and inequalities. | |
# To avoid messy indexing of lbg and ubg, | |
# we use a structure for conveniece | |
g = struct_MX([ | |
entry("collocation",expr=g), | |
entry("obstacleA",expr=[ norm22(p[:2]-pA[:2]) for p in W["X",:,"p"] ]), | |
entry("obstacleB",expr=[ norm22(p[:2]-pB[:2]) for p in W["X",:,"p"] ]) | |
]) | |
# Construct the objective | |
# Reference quaternion orientation: upright position | |
qref = array([0,0,0,1]) | |
# To avoid the quadcopter getting lost in spinning or upside down trajectories, | |
# we add some extra regularisation | |
R2 = vertcat(W["U",:]+W["X",:,"w"]+[ q-qref for q in W["X",:,"q"] ]) | |
alpha = 0.025 | |
# Time optimality | |
f = T + (T/N) * alpha*mul(R2.T, R2) | |
# Create the NLP | |
nlp = MXFunction( nlpIn(x=W), nlpOut(f=f, g=g) ) | |
nlp.init() | |
# Create an IPOPT NLP solver | |
solver = NlpSolver("ipopt",nlp) | |
solver.setOption("linear_solver","mumps") # todo: remove | |
# If we need more than 100 iterations, something is wrong | |
solver.setOption("max_iter",200) | |
solver.init() | |
lbg = g(0) | |
ubg = g(0) | |
# Set the bounds on the obstacle constraints | |
lbg["obstacleA"] = rs[0]**2 | |
lbg["obstacleB"] = rs[1]**2 | |
ubg["obstacleA"] = inf | |
ubg["obstacleB"] = inf | |
solver.setInput(lbg, "lbg") | |
solver.setInput(ubg, "ubg") | |
# Construct and populate the vectors with | |
# upper and lower bounds simple bounds | |
lbx = W(-inf) | |
ubx = W(inf) | |
... | |
x0 = W(0) | |
# Initialize with the solution of tracking | |
... | |
solver.setInput(x0, "x0") | |
solver.setInput(lbx, "lbx") | |
solver.setInput(ubx, "ubx") | |
# Solve the NLP | |
solver.evaluate() | |
# Cast the result vector in a form | |
# that we can easily access | |
sol = W(solver.getOutput("x")) | |
X = sol["X",:,"p","x"] | |
Y = sol["X",:,"p","y"] | |
Z = sol["X",:,"p","z"] | |
# 2D plots | |
ts = linspace(0,sol["T"],N+1) | |
figure() | |
plot(ts,X,label="p_x") | |
plot(ts,Y,label="p_y") | |
plot(ts,Z,label="p_z") | |
xlabel("Time [s]") | |
legend(loc="upper left") | |
title("State trajectories") | |
figure() | |
plot(X,Y,'.',label="optimized") | |
for p, r in zip(ps,rs): | |
gca().add_patch(Circle(p[:2],r,color='red')) | |
gca().add_patch(Circle(p[:2],r,color='red')) | |
legend() | |
title("Top down trajectory view") | |
xlabel("x [m]") | |
xlabel("y [m]") | |
axis('equal') | |
figure() | |
step(ts,horzcat(sol["U",:]+[ sol["U",-1] ]).T,where='post') | |
xlabel("Time [s]") | |
title("Control trajectories") | |
ylim([-0.1,0.6]) | |
if show_3d: | |
# 3D plots | |
from mpl_toolkits.mplot3d import Axes3D | |
figure() | |
ax = gca(projection='3d') | |
ax.plot(array(X),array(Y),array(Z),"b.",label="optimized") | |
# Plot the rotors | |
circle = array([ [cos(t),sin(t),0] for t in linspace(0,2*pi) ]).T*0.05 | |
for p, q in zip(sol["X",::30,"p"],sol["X",::30,"q"]): | |
for offset in [ array([[1,0,0]]),array([[0,1,0]]),array([[-1,0,0]]),array([[0,-1,0]]) ]: | |
circle_offset = circle + 0.1*offset.T | |
circle_3D = mul(quat(*q), circle_offset) | |
ax.plot( | |
array(p[0]+circle_3D[0,:]).squeeze(), | |
array(p[1]+circle_3D[1,:]).squeeze(), | |
array(p[2]+circle_3D[2,:]).squeeze(), | |
'k' | |
) | |
# plot the obstacles | |
for p, r in zip(ps,rs): | |
x=linspace(-1,1,40) | |
z=linspace(-3,3,40) | |
Xc, Zc= meshgrid(x,z) | |
Yc=sqrt(1-Xc**2)*r | |
ax.plot_wireframe(p[0]+Xc*r,p[1]+Yc,Zc,color='red') | |
ax.plot_wireframe(p[0]+Xc*r,p[1]-Yc,Zc,color='red') | |
ax.set_xlim([-pi,pi]) | |
ax.set_ylim([0,2*pi]) | |
ax.set_zlim([-pi,pi]) | |
show() | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from casadi import * | |
from quadcopter import * | |
from casadi.tools import * | |
from pylab import * | |
show_3d = False | |
# Construct the quadcopter model | |
model = Quadcopter() | |
# Get the ode model function | |
f = model.f | |
T = 3.0 | |
N = 200 | |
DT = T/N | |
# This constructs an object that behaves like an MX, | |
# but has convenient accessors | |
# e.g W["X",0] returns x_0 (the state at the first control interval) | |
# | |
# For more information about this CasADi feature, | |
# you could consult an online tutorial | |
# docs.casadi.org -> scroll to bottom | |
# -> click tutorials | |
# -> tools | |
# -> structure.pdf | |
# | |
# At this point, do not attempt to fully | |
# understand this, but just proceed and learn by example | |
W = struct_symMX([ | |
( | |
entry("X",struct=model.x,repeat=N+1), | |
entry("Y",struct=model.x,repeat=N), | |
entry("U",struct=model.u,repeat=N) | |
) | |
]) | |
ts = [0] | |
t = 0 | |
# Build up the list of constraints | |
g = [] | |
for i in range(N): | |
[xdot] = f([ W["Y",i], W["U",i] ]) | |
g.append(...) # TODO: fill in | |
g.append(...) | |
t = t + DT | |
ts.append(t) | |
g = vertcat(g) | |
# Construct the reference to track | |
traj = [ array([sin(i*DT*2*pi/3),i*DT*2*pi/3,0]) for i in range(N) ] | |
# Construct the objective | |
# Fill in | |
# Hint: build up a list of expressions first, and then vertcat | |
R1 = ... | |
R2 = ... | |
alpha = 0.05 | |
obj = (T/N) * ( mul(R1.T, R1) + alpha*mul(R2.T, R2) ) | |
# Create the NLP | |
nlp = MXFunction( nlpIn(x=W), nlpOut(f=obj, g=g) ) | |
nlp.init() | |
# Create an IPOPT NLP solver | |
solver = NlpSolver("ipopt",nlp) | |
solver.setOption("max_iter",100) | |
solver.init() | |
# All constraints are equality constraints in this case | |
solver.setInput(0, "lbg") | |
solver.setInput(0, "ubg") | |
# Construct and populate the vectors with | |
# upper and lower simple bounds | |
# | |
# lbx is an array in disguise. | |
# You can view the underlying array as lbx.cat | |
lbx = W(-inf) | |
ubx = W(inf) | |
# 0 <= u_k <= 0.5 | |
lbx["U",:] = 0 | |
ubx["U",:] = 0.5 | |
# p_0 = [0,0,0]^T | |
lbx["X",0,"p"] = 0.0 | |
ubx["X",0,"p"] = 0.0 | |
# v_0 = [0,0,0]^T | |
... | |
... | |
# Construct a vector with the initial guess | |
x0 = W(0) | |
x0["X",:] = model.x0 | |
... | |
... | |
# Extra info: model.x0 is just an array in disguise, | |
# you can look through the disguise by doing: | |
# print model.x0.cat | |
solver.setInput(x0, "x0") | |
solver.setInput(lbx, "lbx") | |
solver.setInput(ubx, "ubx") | |
# Solve the NLP | |
solver.evaluate() | |
# Cast the result vector in a form | |
# that we can easily access | |
sol = W(solver.getOutput("x")) | |
# Save solution to a file | |
import pickle | |
pickle.dump(sol,file('tracking.pkl','w')) | |
X = sol["X",:,"p","x"] | |
Y = sol["X",:,"p","y"] | |
Z = sol["X",:,"p","z"] | |
# 2D plots | |
figure() | |
plot(ts,X,label="p_x") | |
plot(ts,Y,label="p_y") | |
plot(ts,Z,label="p_z") | |
plot(ts[:-1],horzcat(traj)[0,:].T,label="p_ref_x") | |
plot(ts[:-1],horzcat(traj)[1,:].T,label="p_ref_y") | |
xlabel("Time [s]") | |
legend(loc="upper left") | |
title("State trajectories") | |
figure() | |
plot(X,Y,'.',label="optimized") | |
plot(horzcat(traj)[0,:].T,horzcat(traj)[1,:].T,'.',label="reference") | |
legend() | |
title("Top down trajectory view") | |
xlabel("x [m]") | |
xlabel("y [m]") | |
axis('equal') | |
figure() | |
step(ts, horzcat(sol["U",:]+[ sol["U",-1] ]).T,where='post') | |
xlabel("Time [s]") | |
title("Control trajectories") | |
ylim([-0.1,0.6]) | |
if show_3d: | |
# 3D plots | |
from mpl_toolkits.mplot3d import Axes3D | |
figure() | |
ax = gca(projection='3d') | |
ax.plot(array(X),array(Y),array(Z),"b.",label="optimized") | |
Traj = array(horzcat(traj)) | |
ax.plot(Traj[:,0],Traj[:,1],Traj[:,2],'g.',label="reference") | |
# Plot the rotors | |
circle = array([ [cos(t),sin(t),0] for t in linspace(0,2*pi) ]).T*0.1 | |
for p, q in zip(sol["X",::20,"p"],sol["X",::20,"q"]): | |
for offset in [ array([[1,0,0]]),array([[0,1,0]]),array([[-1,0,0]]),array([[0,-1,0]]) ]: | |
circle_offset = circle + 0.1*offset.T | |
circle_3D = mul(quat(*q), circle_offset) | |
ax.plot( | |
array(p[0]+circle_3D[0,:]).squeeze(), | |
array(p[1]+circle_3D[1,:]).squeeze(), | |
array(p[2]+circle_3D[2,:]).squeeze(), | |
'k' | |
) | |
ax.set_xlim([-pi,pi]) | |
ax.set_ylim([0,2*pi]) | |
ax.set_zlim([-pi,pi]) | |
legend() | |
show() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment