I am trying to solve a low thrust optimal control problem for Earth orbits, i.e. going from one orbit to another. The formulation of the problem includes six states (r_1, r_2, r_3, v_1, v_2, v_3) and 3 controls (u_1, u_2, u_3) with a simplified point model of gravity. When I specify the full initial state and half of the final state, the solver converges and yields a good solution. When I try the full final state, the problem is over constrained.
My thought on how to remedy this is to allow the trajectory to depart the initial orbit at any point along the orbital curve and join the final orbit an any point along the final orbital curve, giving it more degrees of freedom. Is there a way to constrain the initial and final values of all 6 states to a cspline curve? This is what I have tried so far:
m = GEKKO(remote=True)
m.time = np.linspace(0, tof, t_steps)
theta_i = m.FV(lb = 0, ub = 360)
theta_f = m.FV(lb = 0, ub = 360)
rx_i = m.Param()
ry_i = m.Param()
rz_i = m.Param()
vx_i = m.Param()
vy_i = m.Param()
vz_i = m.Param()
rx_f = m.Param()
ry_f = m.Param()
rz_f = m.Param()
vx_f = m.Param()
vy_f = m.Param()
vz_f = m.Param()
m.cspline(theta_i, rx_i, initial.theta, initial.r[:, 0])
m.cspline(theta_i, ry_i, initial.theta, initial.r[:, 1])
m.cspline(theta_i, rz_i, initial.theta, initial.r[:, 2])
m.cspline(theta_i, vx_i, initial.theta, initial.v[:, 0])
m.cspline(theta_i, vy_i, initial.theta, initial.v[:, 1])
m.cspline(theta_i, vz_i, initial.theta, initial.v[:, 2])
m.cspline(theta_f, rx_f, final.theta, final.r[:, 0])
m.cspline(theta_f, ry_f, final.theta, final.r[:, 1])
m.cspline(theta_f, rz_f, final.theta, final.r[:, 2])
m.cspline(theta_f, vx_f, final.theta, final.v[:, 0])
m.cspline(theta_f, vy_f, final.theta, final.v[:, 1])
m.cspline(theta_f, vz_f, final.theta, final.v[:, 2])
r1 = m.Var(rx_i)
r2 = m.Var(ry_i)
r3 = m.Var(rz_i)
r1dot = m.Var(vx_i)
r2dot = m.Var(vy_i)
r3dot = m.Var(vz_i)
u1 = m.Var(lb = -max_u, ub = max_u)
u2 = m.Var(lb = -max_u, ub = max_u)
u3 = m.Var(lb = -max_u, ub = max_u)
m.Equation(r1.dt() == r1dot)
m.Equation(r2.dt() == r2dot)
m.Equation(r3.dt() == r3dot)
r = m.Intermediate(m.sqrt(r1**2 + r2**2 + r3**2))
v = m.Intermediate(m.sqrt(r1dot**2 + r2dot**2 + r3dot**3))
m.Equation(-mu*r1/r**3 == r1dot.dt() + u1)
m.Equation(-mu*r2/r**3 == r2dot.dt() + u2)
m.Equation(-mu*r3/r**3 == r3dot.dt() + u3)
m.fix_final(r1, rx_f)
m.fix_final(r2, ry_f)
m.fix_final(r3, rz_f)
m.fix_final(r1dot, vx_f)
m.fix_final(r2dot, vy_f)
m.fix_final(r3dot, vz_f)
m.Minimize(m.integral(u1**2 + u2**2 + u3**2))
m.options.IMODE = 6
m.options.solver = 3
#m.options.ATOL = 1e-3
m.options.MAX_ITER = 300
m.solve(disp=True) # solve
With cspline, I am trying to allow GEKKO to pick a fixed value of the true anomaly (the parameter that determines how far along the orbit you are) from data that I have generated about the states at sampled true anomalies that would have associated position and velocity states. Any help would be much appreciated!
SOLUTION:
I implemented the end constraints as "soft constraints", i.e. quantities to be minimized, instead of hard constraints which would use fix_final. Specific implementation is as follows.
final = np.zeros(len(m.time))
final[-1] = 1
final = m.Param(value=final)
m.Obj(final*(r1-final_o.r[0, 0])**2)
m.Obj(final*(r2-final_o.r[0, 1])**2)
m.Obj(final*(r3-final_o.r[0, 2])**2)
m.Obj(final*(r1dot-final_o.v[0, 0])**2)
m.Obj(final*(r2dot-final_o.v[0, 1])**2)
m.Obj(final*(r3dot-final_o.v[0, 2])**2)
final makes the solver only consider the last item (the end boundary) when multiplied.
