Mejoras en el script para la optimización de trayectoria de quemado finito con MATLAB

Escribí un script de MATLAB para usar el solucionador de ecuaciones de límite de dos puntos bvp4c para implementar una optimización de trayectoria de Cálculo de variaciones del problema de una sola quemadura finita con una costa libre para elegir el punto de ignición óptimo. Me gustaría cualquier aporte sobre los errores que he cometido o formas de mejorar el código. Lo que hace es resolver el quemado finito óptimo desde una órbita de 185x185 km hasta una órbita de 185x1000 km, con un cambio de inclinación opcional. Establece un problema de valor límite multipunto para resolver un problema de costa quemada utilizando la ecuación del vector de imprimación.

El código se basa en el código de MATLAB en los apéndices de Optimal Control with Aerospace Applications de Longuski, Guzman y Prussing. El tiempo de inercia y los tiempos de quema se han implementado como parámetros libres y el problema del valor límite utiliza el tiempo normalizado (tau) de modo que [0,1] es el período de inercia y [1,2] es el período de quema.

La ODE implementa las ecuaciones de movimiento y la ecuación del vector primario para el movimiento de la fuerza central en coordenadas cartesianas 3D. Creo que los he entendido bien, pero me vendría bien que alguien revisara mi trabajo dos veces. La multiplicación por la costa o tiempos de quema en el valor de retorno de la ODE es necesaria para convertir la derivada de d d t a d d τ .

Las condiciones de contorno son algo sobre lo que tengo una duda sobre si lo he hecho correctamente. La posición inicial, la velocidad y la masa son condiciones triviales. También hay 14 condiciones que imponen continuidad en todas las variables del lado izquierdo y del lado derecho entre la costa y la quema (tau = 1).

Las condiciones restantes son las 5 restricciones en las condiciones orbitales terminales, y luego las 3 condiciones de transversalidad para dar cuenta de la verdadera anomalía de que el punto de conexión esté libre y los tiempos de la costa y la quema estén libres. Para las condiciones orbitales terminales, estoy usando el vector de momento angular y los dos primeros componentes del vector de excentricidad son iguales a la órbita objetivo. Lo que estoy usando para las condiciones de transversalidad es que si el hamiltoniano se divide en H = H 0 + T S donde T es el empuje y S la función de conmutación, entonces configuro H 0 ( t 2 ) = 0 , H 0 ( t 0 ) = 0 y luego con S = ( | pag v | 1 ) colocar | pag v ( t 1 ) | = 1 . Me falta algo de confianza en que lo haya entendido del todo bien.

Los resultados parecen ser bastante decentes, pero estoy un poco sorprendido de que no veo | pag v ( t 2 ) | = 1 . También veo problemas de convergencia para cualquier problema que involucre una inclinación superior a unos 25 grados (pero eso se convierte en quemaduras de 300 segundos / 3000 dV).

Todavía no he normalizado las unidades de posición y velocidad, por lo que todavía están en metro y metro / s .

Para quemados más moderados, parece funcionar bastante bien y brinda tiempos de quemado dentro de aproximadamente un segundo del modelo de quemado impulsivo y parece que lidera el quemado correctamente con una costa negativa de aproximadamente la mitad del tiempo de quemado.

Sin embargo, agradecería cualquier ayuda para encontrar errores que he cometido o formas de mejorar este código para hacerlo más sólido.

close all; clear all; clc;

global r0 v0 m0 rT vT g0
global MU Thrust Mdot

MU = 3.9860044189e+14;  % earth
Thrust = 232.7 * 1000;  % N; LR-91 232.7 kN
m0 = 32.74 * 1000;      % kg; 32.74t - 1g start accel
isp = 316;              % sec; LR-91
g0 = 9.80665;           % m/s; standard gravity
ve = isp * g0;          % m/s
a0 = Thrust / m0        % m/s^2; initial accel
tau = ve / a0           % s; time to burn rocket to zero
Mdot = Thrust / ve;     % kg/sec

rearth = 6.371e+6;      % m; earth radius
r185 = rearth + 0.185e+6;
r1000 = rearth + 1.000e+6;

% initial position (185x185)
r0 = [ r185, 0, 0 ];
v185 = sqrt(MU/r185);
v0 = [ 0, v185, 0 ];

% target orbital parameters (185x1000)
rT = [ r185, 0, 0 ];
smaT = ( r185 + r1000 ) / 2;
inc = 10;  % degrees
vTm = sqrt(MU * (2/r185 - 1/smaT) );
vT = [ 0, vTm * cosd(inc), vTm * sind(inc)];

vBurn = vT - v0;
dV = norm(vBurn)

% list initial conds
yinit = [r0 v0 vBurn/norm(vBurn) 0 0 0 m0 ];  % initial state and costate
tb_guess = tau * (1 - exp(-dV/ve) )
tc_guess = - tb_guess / 2

% sanity checks on the impulsive burn model
mf_impulsive = m0 - tb_guess * Mdot
af_impulsive = Thrust / mf_impulsive / g0

% parameters
parameters = [ tc_guess, tb_guess ];

% number of timeslices
Nt = 41;
tau = [
  linspace(0,1,Nt)'
  linspace(1,2,Nt)'
];

% initial guess
solinit = bvpinit(tau, yinit, parameters);

% bump up the mesh by a bit
option=bvpset('Nmax',50000);

% solve
sol = bvp4c(@Burn_odes, @Burn_bcs, solinit, option);

% extract times
tc = sol.parameters(1)
tb = sol.parameters(2)

% pull out values for times
Z = deval(sol, tau);

% convert taus to times
for i=1:length(tau)
  if tau(i) <= 1
    time(i) = tau(i) * tc;
  else
    time(i) = tc + ( tau(i) - 1 ) * tb;
  end
end

% extract solution vars
x_sol = Z(1,:);
y_sol = Z(2,:);
z_sol = Z(3,:);
r_sol = sqrt( x_sol.^2 + y_sol.^2 + z_sol.^2 );
vx_sol = Z(4,:);
vy_sol = Z(5,:);
vz_sol = Z(6,:);
v_sol = sqrt( vx_sol.^2 + vy_sol.^2 + vz_sol.^2 );
pvx_sol = Z(7,:);
pvy_sol = Z(8,:);
pvz_sol = Z(9,:);
pv_sol = sqrt( pvx_sol.^2 + pvy_sol.^2 + pvz_sol.^2 );
m_sol = Z(13,:);

for i = 1:length(tau)
  r = Z(1:3, i);
  v = Z(4:6, i);
  h(i) = norm(cross(r,v));
end

% plots

figure;
subplot(3,2,1);
plot(time,m_sol/1000);
ylabel('mass, tons', 'fontsize', 14);
xlabel('Time, sec', 'fontsize', 14);
hold on;
grid on;

subplot(3,2,2);
plot(time,r_sol/1000);
ylabel('radius, km', 'fontsize', 14);
xlabel('Time, sec', 'fontsize', 14);
hold on;
grid on;

subplot(3,2,3);
plot(time,v_sol/1000);
ylabel('velocity, km/s', 'fontsize', 14);
xlabel('Time, sec', 'fontsize', 14);
hold on;
grid on;

subplot(3,2,4);
plot(time,h);
ylabel('h', 'fontsize', 14);
xlabel('Time, sec', 'fontsize', 14);
hold on;
grid on;

subplot(3,2,5);
plot(time,pv_sol);
ylabel('pv magnitude', 'fontsize', 14);
xlabel('Time, sec', 'fontsize', 14);
hold on;
grid on;

figure;
plot(x_sol/1000, y_sol/1000);
grid on;
axis equal
xlabel('x, km', 'fontsize', 14);
ylabel('y, km', 'fontsize', 14);


%
% Boundary conditions
%

function PSI = Burn_bcs(yleft, yright, parameters)

Y0 = yleft(:,1);
Y1 = yleft(:,2);  % == yright(:,1)
Y2 = yright(:,2);

global r0 v0 m0 rT vT MU g0 Thrust

ri = Y0(1:3);
vi = Y0(4:6);
pvi = Y0(7:9);
pri = Y0(10:12);
mi = Y0(13);
ri3 = norm(ri)^3;

rf = Y2(1:3);
vf = Y2(4:6);
pvf = Y2(7:9);
prf = Y2(10:12);
mf = Y2(13);
rf3 = norm(rf)^3;

r1 = Y1(1:3);
v1 = Y1(4:6);
pv1 = Y1(7:9);
pr1 = Y1(10:12);
r13 = norm(r1)^3;

hT = cross(rT, vT);
hf = cross(rf, vf);

eT = - (rT/norm(rT) + cross(hT,vT)/MU);
ef = - (rf/norm(rf) + cross(hf,vf)/MU);

emiss = ef - eT';

uf = pvf / norm(pvf);

H0ti = dot(pri, vi) - MU * dot(pvi, ri) / ri3;
H0t1 = dot(pr1, v1) - MU * dot(pv1, r1) / r13;
H0tf = dot(prf, vf) - MU * dot(pvf, rf) / rf3;
Htf = H0tf - Thrust * ( norm(pvf) - 1 );

PSI = [
    Y0(1:3) - r0'                       % 3 - initial position
    Y0(4:6) - v0'                       % 3 - initial velocity
    Y0(13) - m0                         % 1 - initial mass
    norm(Y1(7:9)) - 1                   % 1 - norm(pv1) = 1 (so H(t1) = 0 on both sides of t1)
    hf - hT'                            % 3 - terminal constraint on angular momentum
    emiss(1:2)                          % 2 - terminal constraint on ecc vector
    H0ti                                % 1 - H0(t0) = 0
    H0tf                                % 1 - H0(tf) = 0
    yleft(:,2) - yright(:,1)            % 14 - values at t1 are continuous
    ];

end

%
% Equations of motion
%

function dX_dtau = Burn_odes(tau, X, k, parameters)

global MU Thrust Mdot

if k == 1
  thr = 0;
  md = 0;
  tinterval = parameters(1);  % tc
else
  thr = Thrust;
  md = Mdot;
  tinterval = parameters(2);  % tb
end

r = X(1:3);
v = X(4:6);
pv = X(7:9);
pr = X(10:12);
m = X(13);

u = pv / norm(pv);

Fm = thr / m;
r3 = norm(r)^3;
r2 = dot(r,r);
r5 = norm(r)^5;

rdot  = v;
vdot  = - MU * r / r3 + Fm * u;
pvdot = pr;
prdot = - MU / r5 * ( 3 * r' * r - r2 * eye(3,3) ) * pv;

dX_dtau = tinterval*[rdot', vdot', pvdot', prdot', -md ];

end
Admito que no estoy muy familiarizado con el trabajo de Longuski (aunque debería hacerlo ya que trabajo en optimización de quemado finito y soluciones subóptimas), así que disculpe las preguntas desinformadas. ¿Podría explicarnos por qué los primeros tres elementos de co-estado son la norma del vector de velocidad (si leí correctamente)? Además, ¿hay alguna razón específica por la que hayas codificado todo en metros en lugar de kilómetros? Además, ¿estás seguro de que este algoritmo TPBVP funciona con grandes cambios de inclinación? Muchos algoritmos de optimización se descomponen por grandes cambios de OE o una vez que se habilitan algunas perturbaciones.
Si se está refiriendo a yinit, esa es solo la suposición inicial en el vector primario costate, que es un vector unitario en la dirección progresiva. la ecuación del vector primario para el estado de la costa se encuentra casi en la parte inferior de la secuencia de comandos.
No hay razón para metros en lugar de km, y creo que debería normalizarse a algo más cercano al radio de las órbitas inicial o objetivo. Hay un ejemplo adicional en el libro que trata sobre eso, y es solo una TODO que aún no he llegado.
Y este es mi primer intento de problemas de optimización adecuados (de cualquier tipo), por lo que no tengo idea de si el algoritmo TPBVP debería funcionar con grandes cambios de inclinación o no. Si se espera que los optimizadores tengan dificultades con ese problema, entonces eso tiene sentido (no tengo idea de cuál es la idoneidad del optimizador bvp4c para este tipo de problema, lo estoy usando principalmente para aprender cómo plantear estos problemas en el primer lugar).
No sé lo suficiente sobre cálculo de variaciones para saber si se descomponen: le preguntaré a algunos colegas que, con suerte, tendrán una respuesta para usted. Sin embargo, lo que puedo afirmar es que un solucionador de Lambert es una forma de resolver el TPBVP y definitivamente funciona para pequeños cambios de inclinación. Y tampoco veo por qué fallaría incluso con cambios de inclinación muy grandes... ¡Esto es desconcertante! ¡Gran pregunta! :-D
Hice un pequeño ajuste esta mañana para cambiar la suposición de que el costate inicial está en la dirección de la solución de quemado impulsivo en lugar de solo en la dirección prograde, pero no parece cambiar mucho.
Es posible que pueda refactorizar esto considerablemente para usar la función ode45() en matlab junto con un par de rutinas jacobianest() y newtonraphson() populares y de apariencia robusta en el intercambio de archivos de Mathworks (y la última parece ser un quasi-newton enfoque que degrada a un ascenso de gradiente, por lo que puede ser apropiado para este tipo de aplicación). El resultado debería ser ampliamente equivalente a todos los enfoques de CoV en la literatura (el tratamiento analítico del período costero y la normalización de las unidades lo terminarían).
Además, al leer sobre problemas de límites multipunto, no sabía que no solo se integra a través de los puntos límite, sino que ejecuta (por ejemplo) Newton-Raphson en sus puntos límite, lo que aumenta la estabilidad (aparentemente obteniendo algunos de los beneficios de los métodos de elementos finitos sin ser tan costosos?). Así que creo que cortar el quemado en incrementos de 200-300 aumentaría la estabilidad. Lo cual concuerda con lo que he leído en la literatura sobre optimización de trayectorias de ascensos.
Agregar un punto adicional fijo de 200 segundos en la quema me llevó a un cambio de inclinación de 30 grados estable (que comienza a empujar el tiempo de inercia hasta 200 segundos). Intentaré dividir dinámicamente el problema en intervalos de 100 segundos tanto en la costa como en la quema y veré cómo va.
Encontré este documento researchgate.net/publication/… que creo que cubre todas las preguntas que tengo. Parece que realmente necesito concentrarme en condicionar el problema para que el estado y el costo se normalicen cerca de cero y creo que he estropeado al menos mi H 0 ( t F ) = 0 condición que debe ser H ( t F ) = 0 y necesito abordar los problemas de escala con esa condición que cubre el documento.
"...normalizado cerca de uno "
El gran error que acabo de encontrar en la ecuación del vector costate / primer es que r' * res solo el producto de punto interno mientras que necesita el r * r'producto externo allí (sabía que necesitaba que solo tenía un error tipográfico y nunca lo verifiqué).
Voto para cerrar esta pregunta como fuera de tema porque se trata de resolver ecuaciones con matlab. Las ecuaciones en sí son relevantes, pero la pregunta no es sobre ellas o cómo se han aplicado.
Las preguntas sobre las condiciones de contorno son aplicables aquí como una pregunta de Mecánica Orbital, diría, simplemente no se destacan lo suficiente ni en la pregunta ni en la auto-respuesta. Dados los votos a favor y los comentarios antes de que los votos cerrados aparecieran meses después, votaré para dejarlo abierto.
@ErinAnne, la pregunta de la condición de límite puede ser aplicable, pero la pregunta tendría que editarse en gran medida para tratarla; de lo contrario, la pregunta debería clasificarse como demasiado amplia.
Probablemente pueda escribir mejor la pregunta ahora, aunque no creo que tenga tiempo ahora para editarla. Sin embargo, la intención de la pregunta es "Estoy tratando de escribir un optimizador de trayectoria de cohete (en matlab) y no sé lo que estoy haciendo, ayuda".
Y va más allá de las cuestiones de valor límite / transversalidad. Empecé tratando de usar el solucionador bpv4c TPBPV de Matlab cuando el método CoV/indirecto que había elegido usar ya convierte un TPBPV en un problema de integración + búsqueda de raíz, por lo que solo usar ode45+fsolve() funcionó mucho mejor. No sé qué estaba haciendo con bpv4c, pero ciertamente no funcionó. También puede haber mejores formas de resolver el problema de optimización de la trayectoria original usando bpv4c de matlab y aún así agradecería que alguien me mostrara cómo hacerlo.
(También estaba buscando específicamente aportes de personas que podrían ser estudiantes de posgrado, o más allá, que optimizan la trayectoria del cohete, no usuarios promedio de matlab: el dominio del problema es mucho más importante que la herramienta)

Respuestas (1)

Finalmente me di cuenta de que al tratar de usar bvp4ceso estaba haciendo que todo el problema fuera más difícil de lo que necesitaba. Al usar CoV, ya he convertido el problema de un TPBVP a un problema de búsqueda de raíces. El buscador de raíces predeterminado fsolveen Matlab es cuasi-newtoniano con una región de confianza dogleg y admite el cálculo de jacobianos numéricos. Eso marca todas las casillas.

Así que actualicé el script anterior para usar fsolve. También he aplicado el condicionamiento numérico que usa Ping Lu .

Parece manejar el caso de hacer una quemadura para pasar de una órbita ecuatorial de 185x185 km a una órbita de 185x1000 con un cambio de inclinación de 89 grados muy bien y produce resultados de aspecto plausible. Solo toma 0.4 segundos en mi escritorio cuando se alimenta la conjetura inicial correspondiente a la quema impulsiva. Esa es una quemadura de 11,089 dV, que se está volviendo bastante tonta en términos de realidad, pero el solucionador lo maneja.

Este script tiene una singularidad en las condiciones de contorno al apuntar a órbitas polares (la referencia de la que obtuve las condiciones de contorno sugiere mover los vectores a través de un cambio de sistema de coordenadas para abordar eso). Es por eso que usé un cambio de 89 grados en lugar de 90 grados.

Creo que la mayor deficiencia numérica aquí es que se trata de un enfoque de disparo único y para múltiples fases de quema de costa que el problema debería usar un enfoque de disparo múltiple para una estabilidad adicional (o para arcos de quema larga o costa).

También estoy lanzando ODE45 para integrar la solución. Sería mejor utilizar la solución analítica de Goodyear para arcos de costa. Ping Lu tiene una serie de documentos sobre diferentes enfoques de aproximaciones analíticas a los arcos de quemado.

Todavía no estoy seguro de tener las 3 condiciones límite para la conexión gratuita y la optimización de la costa y los tiempos de encendido correctos; simplemente necesito volver a esos ahora que tengo una solución de optimización más estable.

También necesito mirar un poco para limpiar las variables en el código para la normalización; estoy bastante seguro de que mi mu_barsiempre es algebraicamente idéntico 1.000..y se puede eliminar por completo, etc.

Para ejecutar este código, se requiere Matlab R2017b (para vecnorm) y la función rv2orb() convierte los vectores de estado en elementos keplerianos y está aquí .

close all; clear all; clc;
format shortG;

mu = 3.9860044189e+14;  % m^3/s^2; earth
thrust = 232.7 * 1000;  % N; kg m / s^2; LR-91 232.7 kN
m0 = 32.74 * 1000;      % kg; 32.74t - 1g start accel
isp = 316;              % sec; LR-91
g0 = 9.80665;           % m/s; standard gravity
ve = isp * g0;          % m/s
a0 = thrust / m0;       % m/s^2; initial accel
tau = ve / a0;          % s; time to burn rocket to zero
mdot = thrust / ve;     % kg/sec

rearth = 6.371e+6;      % m; earth radius
r185   = rearth + 0.185e+6;
r1000  = rearth + 1.000e+6;
v185   = sqrt(mu/r185); % 185x185 velocity

% initial conditions
r0 = [ r185, 0, 0 ];
v0 = [ 0, v185, 0 ];

% target conditions
rT = [ r185, 0, 0 ];
smaT = ( r185 + r1000 ) / 2;
inc = 89; % degrees
vTm = sqrt(mu * (2/r185 - 1/smaT) );
vT = [ 0, vTm * cosd(inc), vTm * sind(inc)];

% impulsive burn approximation
dV = vT - v0;
dVm = norm(dV)
burnTime = tau * (1 - exp(-dVm/ve) )

% scaling factors
g_bar = mu / norm(r0)^2;
r_scale = norm(r0);
v_scale = sqrt( norm(r0) * g_bar );
t_scale = sqrt( norm(r0) / g_bar );

% applying scaling
tb_bar     = burnTime / t_scale;
tc_bar     = - burnTime / t_scale / 2;
r0_bar     = r0 / r_scale;
rT_bar     = rT / r_scale;
v0_bar     = v0 / v_scale;
vT_bar     = vT / v_scale;
mu_bar     = mu / r_scale^3 * t_scale^2;    % this is just always == 1.000 tho right?
thrust_bar = thrust / r_scale * t_scale^2;  % can these two...
mdot_bar   = mdot * t_scale;                % ...get simplified?

% solve the problem
fun = @(x) coastBurn5constraintFun(r0_bar, v0_bar, x(1:3), x(4:6), m0, x(7), x(8), mu_bar, thrust_bar, mdot_bar, rT_bar, vT_bar);

tic
[x, z, exitflag, output, jacobian] = fsolve(fun, [ dV/norm(dV) zeros(1,3) tc_bar tb_bar ]);
toc

disp(z');

disp(output);

% use the solution to pull data out of the IVP again
xinit = [ r0_bar v0_bar x(1:3) x(4:6) m0 ];
tc_bar = x(7);
tb_bar = x(8);
[x, tfull, xfull] = coastBurnIVP(xinit, tc_bar, tb_bar, mu_bar, thrust_bar, mdot_bar);

% reverse scaling
tfull = tfull * t_scale;
xfull(:,1:3) = xfull(:,1:3) * r_scale;
xfull(:,4:6) = xfull(:,4:6) * v_scale;

% print some output
rf = xfull(end, 1:3)
vf = xfull(end, 4:6)

tc = tc_bar * t_scale
tb = tb_bar * t_scale

[a,eMag,i,O,o,nu,truLon,argLat,lonPer,p] = rv2orb(rf', vf', mu);

inc = rad2deg(i)

PeR = (1 - eMag) * a
ApR = (1 + eMag) * a

PeA = PeR - rearth
ApA = ApR - rearth

% plots

figure;
subplot(3,2,1);
plot(tfull,xfull(:,13)/1000);
ylabel('mass, tons', 'fontsize', 14);
xlabel('Time, sec', 'fontsize', 14);
hold on;
grid on;

subplot(3,2,2);
plot(tfull,vecnorm(xfull(:,1:3)')'/1000);
ylabel('radius, km', 'fontsize', 14);
xlabel('Time, sec', 'fontsize', 14);
hold on;
grid on;

subplot(3,2,3);
plot(tfull,vecnorm(xfull(:,4:6)')'/1000);
ylabel('velocity, km/s', 'fontsize', 14);
xlabel('Time, sec', 'fontsize', 14);
hold on;
grid on;

%subplot(3,2,4);
%plot(time,h);
%ylabel('h', 'fontsize', 14);
%xlabel('Time, sec', 'fontsize', 14);
%hold on;
%grid on;

subplot(3,2,5);
plot(tfull,vecnorm(xfull(:,7:9)')');
ylabel('pv magnitude', 'fontsize', 14);
xlabel('Time, sec', 'fontsize', 14);
hold on;
grid on;

%figure;
%plot(x_sol/1000, y_sol/1000);
%grid on;
%axis equal
%xlabel('x, km', 'fontsize', 14);
%ylabel('y, km', 'fontsize', 14);


% IVP function returning the 5-constraint BC miss
%
function z = coastBurn5constraintFun(r0_bar, v0_bar, pv, pr, m0, tc_bar, tb_bar, mu_bar, thrust_bar, mdot_bar, rT_bar, vT_bar)
  xinit = [ r0_bar v0_bar pv pr m0 ];
  [x, tfull, xfull] = coastBurnIVP(xinit, tc_bar, tb_bar, mu_bar, thrust_bar, mdot_bar);
  z = coastBurn5constraintBC(x, mu_bar, rT_bar, vT_bar);
end

% 5 orbital constraint Boundary Conditions for Coast-Burn with free times
%
function z = coastBurn5constraintBC(x, mu_bar, rT, vT)
  x0 = x(1,:);
  x1 = x(2,:);
  x2 = x(3,:);

  r0 = x0(1:3);
  v0 = x0(4:6);
  pv0 = x0(7:9);
  pr0 = x0(10:12);

  r1 = x1(1:3);
  v1 = x1(4:6);
  pv1 = x1(7:9);
  pr1 = x1(10:12);

  r2 = x2(1:3);
  v2 = x2(4:6);
  pv2 = x2(7:9);
  pr2 = x2(10:12);

  hT = cross(rT, vT);
  h2 = cross(r2, v2);

  hmiss = h2 - hT;

  eT = - (rT/norm(rT) + cross(hT, vT)/mu_bar);
  e2 = - (r2/norm(r2) + cross(h2, v2)/mu_bar);

  emiss = e2 - eT;

  H0t1 = dot(pr1, v1) - mu_bar * dot(pv1, r1) / norm(r1)^3;
  H0t2 = dot(pr2, v2) - mu_bar * dot(pv2, r2) / norm(r2)^3;

  z = [
    hmiss'          % 3 constraints
    emiss(1:2)'     % 2 constraints
    H0t1
    H0t2
    norm(pv0) - 1
  ];
end

% Multi step integration of the IVP
%
function [x, tfull, xfull] = coastBurnIVP(x0, tc, tb, mu, thrust, mdot)
  coastfun = @(t,x) centralForceThrustEOM(t, x, mu, 0, 0);  % FIXME: Use Goodyear instead of ODE45
  [t1, x1] = ode45(coastfun, [0 tc], x0);

  burnfun = @(t,x) centralForceThrustEOM(t, x, mu, thrust, mdot);
  [t2, x2] = ode45(burnfun, [tc tc+tb], x1(end,:));

  % x values at the boundaries
  x = [
    x1(1,:)
    x2(1,:)
    x2(end,:)
  ];

  % all the t's for graphing
  tfull = [
    t1
    t2
  ];

  % all the x's for graphing
  xfull = [
    x1
    x2
  ];
end

% Equations of Motion
%
function dX_dt = centralForceThrustEOM(t, X, mu, thrust, mdot)
  r  = X(1:3);
  v  = X(4:6);
  pv = X(7:9);
  pr = X(10:12);
  m  = X(13);

  u = pv/norm(pv);
  Fm = thrust / m;

  r3 = norm(r)^3;
  r2 = dot(r,r);
  r5 = r2 * r3;

  rdot  = v;
  vdot  = - mu * r / r3 + Fm * u;
  pvdot = pr;
  prdot = - mu / r5 * ( 3 * r * r' - r2 * eye(3,3) ) * pv;

  dX_dt = [ rdot' , vdot', pvdot', prdot', -mdot ]';
end
etiquetando a @ChrisR
Convertí la secuencia de comandos anterior para realizar la búsqueda de raíces multipunto, que resultó ser conceptualmente más simple de lo que esperaba. Aunque eliminé las variables fijas (r0, v0 y m) todavía expande el número de ecuaciones de 8 a 20 y el tiempo para resolver va de 0.4s a 1.4s.
Eliminar las variables parece ser una microoptimización innecesaria. Expandirlo a un problema de resolución de raíz de 28 dimensiones no parece costar mucho (dado que son las condiciones iniciales, el jacobiano es trivial y la suposición inicial es exacta). Eso comienza a simplificar el código. La implementación de Levenburg-Marquardt fsolve() también parece adecuada para resolver este tipo de problema.