I have a function for use with ode45. The function returns velocity and acceleration, and I use ode45 to solve it to obtain position and velocity.
within the function, I define "Force" by multiplying acceleration with mass, and I want to extract this, outside the function to plot it.
I have tried two methods.
The first method is this (code down below), but it returns errors in matrix calculations.
[~, Force] = constrained_ECI(t, result.', G, M, i, ohm, n)
matrix
R_bar = [- sin(t.*nval).*cos(ohmval) - cos(t.*nval).*cos(ival).*sin(ohmval), cos(t.*nval).*cos(ival).*cos(ohmval) - sin(t.*nval).*sin(ohmval), cos(t.*nval).*sin(ival); ...
sin(ival)*sin(ohmval), -cos(ohmval)*sin(ival), cos(ival)]
an issue with storing only the first columns in each 2x3 array like this.
...
-0.0846 0.1517 0.9848
-0.0854 0.1512 0.9848
-0.0858 0.1510 0.9848
-0.0861 0.1508 0.9848
-0.0865 0.1506 0.9848
-0.0868 0.1504 0.9848
0.4924 -0.8529 0.1736
have to get exact result like this.
...
-0.2607 -0.3349 -0.9055
0.4924 -0.8529 0.1736
-0.2607 -0.3349 -0.9055
0.4924 -0.8529 0.1736
...
The second method involves using cellfun,
[~,Force] = cellfun(@(t,x) constrained_ECI(t, result.', G, M, i, ohm, n), num2cell(t), mat2cell(result, ones(size(result, 1), 1), size(result, 2)), 'UniformOutput', false)
ForceData = cell2mat(Force);
which runs without errors but gives wrong results.
Is there a problem with the cellfun I used? Any assistance would be appreciated. Thank you.
My Full Code
clc; clear;
%% parameters
G = 6.67430e-11; % J*m/kg^2 Gravitational constant
M = 5.9722e24; % kg, Earth Mass
rho = 50000;
w0 = 0;
i = deg2rad(80);
ohm = deg2rad(30);
r0 = 7e6; % radius of chief
r0vector = [r0;0;0];
%% time
n = 0.001078; % rad/s
p = 2*pi/n; % period
tspan = [0 p*3]; % Time span for the simulation
opts = odeset('RelTol',1e-12,'AbsTol',1e-14);
%% Initial conditions
% x0 y0 z0 xdot0 ydot0 zdot0
alpha0 = 0.55;
x0 = rho*sin(alpha0)/2;
y0 = rho*cos(alpha0);
z0 = rho*sin(alpha0);
xdot0 = rho*n*cos(alpha0)/2;
ydot0 = -rho*n*sin(alpha0);
zdot0 = rho*n*cos(alpha0);
xyz0 = [x0;y0;z0];
xyzdot0 = [xdot0;ydot0;zdot0];
hillxyz0 = xyz0 + r0vector;
%% rotation matrix
syms nval time ival ohmval real
t0 = 0;
wval = w0 + nval*(time-t0);
R3w = [cos(wval) sin(wval) 0; -sin(wval) cos(wval) 0; 0 0 1];
R1i = [1 0 0; 0 cos(ival) sin(ival); 0 -sin(ival) cos(ival)];
R3ohm = [cos(ohmval) sin(ohmval) 0; -sin(ohmval) cos(ohmval) 0; 0 0 1];
R_cal = R3w * (R1i * R3ohm);
% hill x y z -> ECI X Y Z
R_0 = subs(R_cal, [nval, ival, ohmval, time], [n, i, ohm, 0]);
ECIXYZ0 = inv(R_0)*hillxyz0;
% hill x' y' z' -> ECI X' Y' Z'
Rdot = diff(R_cal, time);
Rdot0 = subs(Rdot, [nval, ival, ohmval, time], [n, i, ohm, 0]);
ECIXYZdot0 = inv(R_0) * (xyzdot0 - Rdot0 * ECIXYZ0);
initial_state = double(vpa([ECIXYZ0; ECIXYZdot0], 4));
%% ===== Solve =====
[t, result] = ode45(@(t, y) constrained_ECI(t, y, G, M, i, ohm, n), tspan, initial_state, opts);
X = result(:, 1);
Y = result(:, 2);
Z = result(:, 3);
ECImatrix = [X Y Z];
%% ===== method 1 =====
[~, Force] = constrained_ECI(t, result.', G, M, i, ohm, n)
%% ===== method 2 =====
[~,Force] = cellfun(@(t,x) constrained_ECI(t, result.', G, M, i, ohm, n), num2cell(t), mat2cell(result, ones(size(result, 1), 1), size(result, 2)), 'UniformOutput', false)
ForceData = cell2mat(Force);
%% ===== function =====
function [state, Force] = constrained_ECI(t, y, G, M, ival, ohmval, nval)
m = 1;
vel = [y(4);y(5);y(6)]; % X dot
r = [y(1);y(2);y(3)]; % X
R_bar = zeros(2,3);
R_bar_dot = zeros(2,3);
R_bar_ddot = zeros(2,3);
R_tilde = zeros(2,3);
R_tilde_dot = zeros(2,3);
R_tilde_ddot= zeros(2,3);
R_bar = [- sin(t*nval)*cos(ohmval) - cos(t*nval)*cos(ival)*sin(ohmval), cos(t*nval)*cos(ival)*cos(ohmval) - sin(t*nval)*sin(ohmval), cos(t*nval)*sin(ival); ...
sin(ival)*sin(ohmval), -cos(ohmval)*sin(ival), cos(ival)]
R_bar_dot = [-nval*cos(ohmval)*cos(t*nval)+nval*sin(ohmval)*cos(ival)*sin(t*nval), -nval*cos(ohmval)*cos(ival)*sin((t*nval))-nval*sin(ohmval)*cos((t*nval)), -nval*sin(ival)*sin(t*nval); ...
0 0 0];
R_bar_ddot = [(nval^2)*cos(ohmval)*sin(t*nval)+(nval^2)*sin(ohmval)*cos(ival)*cos(t*nval), -(nval^2)*cos(ohmval)*cos(ival)*cos(t*nval)+(nval^2)*sin(ohmval)*sin(t*nval), -(nval^2)*sin(ival)*cos(t*nval); ...
0 0 0];
R_tilde = [cos(t*nval)*cos(ohmval) - sin(t*nval)*cos(ival)*sin(ohmval), cos(t*nval)*sin(ohmval) + sin(t*nval)*cos(ival)*cos(ohmval), sin(t*nval)*sin(ival); ...
sin(ival)*sin(ohmval), -cos(ohmval)*sin(ival), cos(ival)];
R_tilde_dot = [-nval*cos(ohmval)*sin(t*nval)-nval*sin(ohmval)*cos(ival)*cos(t*nval), -nval*sin(ohmval)*sin(t*nval)+nval*cos(ohmval)*cos(ival)*cos(t*nval), nval*sin(ival)*cos(t*nval); ...
0 0 0];
R_tilde_ddot = [-(nval^2)*cos(ohmval)*cos(t*nval)+(nval^2)*sin(ohmval)*cos(ival)*sin(t*nval), -(nval^2)*sin(ohmval)*cos(t*nval)-(nval^2)*cos(ohmval)*cos(ival)*sin(t*nval), -(nval^2)*sin(ival)*sin(t*nval); ...
0 0 0];
%% [A11 A12 A13]
Arow1 = (r')*((R_bar')*R_bar);
A11 = Arow1(1);
A12 = Arow1(2);
A13 = Arow1(3);
%% [A21 A22 A23]
Arow2 = [2, -1]*R_tilde;
A21 = Arow2(1);
A22 = Arow2(2);
A23 = Arow2(3);
%% [b1 b2]
b1 = -(r')*(R_bar')*R_bar_ddot*(r) - 2*(r')*(R_bar')*R_bar_dot*(vel)...
-(r')*(R_bar_dot')*R_bar_dot*(r) - 2*(r')*(R_bar_dot')*R_bar*(vel)...
-(vel')*(R_bar')*R_bar*(vel);
b2 = -[2 -1]*R_tilde_ddot*(r) - [4 -2]*R_tilde_dot*(vel);
Amatrix = [A11 A12 A13;A21 A22 A23];
Bmatrix = [b1;b2];
A1MPinverse = (1/(A11^2+A12^2+A13^2))*(Amatrix(1,:)');
Bbeta = (A11*A21 + A12*A22 + A13*A23)/(A11^2+A12^2+A13^2);
cMPinverse = (1/(A21^2+A22^2+A23^2-Bbeta^2*(A11^2 + A12^2+A13^2)))*((Amatrix(2,:)')-Bbeta*(Amatrix(1,:)'));
AMPinverse = [(A1MPinverse - Bbeta*cMPinverse) cMPinverse];
rnorm = norm(r);
unconstrained_accel = -(G*M/rnorm^3)*r;
constrained_accel = (unconstrained_accel) ...
+ (AMPinverse*Bmatrix) ...
+ ((G*M/rnorm^3)*AMPinverse*Amatrix*r);
%% state, Force
Force = m*(constrained_accel - unconstrained_accel)
state = [vel;constrained_accel];
end
If you're using constrained_ECI
with ode45
then you need to match the format expected by ode45
(see the documentation) and not add extra outputs. There are few ways around this.
Maybe the simplest and best way to do this is to just create two separate functions: one to use for numerical integration and the other to calculate the value you want base on the integration results. This has the advantage not including additional unneeded computations within your ODE function. If you do need the result of this other function inside your ODE you can the second function there if you like. Here's an example of this with a simpler ODE system:
c = -0.51;
tspan = [0 12];
y0 = [0; 1; 1];
[t,y] = ode45(@(t,y)f1(t,y,c),tspan,y0);
some_other_result1 = other_result1_func(t,y,c);
function out = f1(t,y,c)
out = [y(2)*y(3); -y(1)*y(3); other_result1_func(t,y.',c)];
end
function out = other_result1_func(t,y,c)
out = c*y(:,1).*y(:,2);
end
Another option is to include some logic within your integration function to switch what it returns. Note however that this may add a bit of extra computation overhead when solving the ODE. Normally, one wouldn't want to include any branching within an ODE function but here only one of the branches should be called during integration. For example:
c = -0.51;
tspan = [0 12];
y0 = [0; 1; 1];
[t,y] = ode45(@(t,y)f2(t,y,c,false),tspan,y0);
some_other_result2 = f2(t,y.',c,true);
function out = f2(t,y,c,is_other_result)
other_result = c*y(1,:).*y(2,:)
if is_intermediate_result
out = other_result.';
else
out = [y(2)*y(3); -y(1)*y(3); other_result];
end
end
Note that in both of the above cases, you'll need to do some vectorization of your functions if you with to pass in the full results vectors from ode45
as nominally the ODE integration function only expects a scale time and state vector at that time.
Finally, there are schemes that could employ global
variables that might work but are generally not recommended due to performance hits and poor coding style.