What do you think of my numerical Jacobian, using the central-difference method?

16 views (last 30 days)
Hi everyone!
Here's my 6x6 numerical Jacobian, using the central-difference method.
For now, I've used the same step-size h for each differentiation variable.
What do you think of it? Any suggestions?
Thanks in advance!
%% write six first-order equations
rdots = [vGx; vGy; omega];
rddots = [aGx; aGy; omegadot];
%% specify step-size
h = .001;
%% use simple function notation
F1 = rdots(1);
F2 = rdots(2);
F3 = rdots(3);
F4 = rddots(1);
F5 = rddots(2);
F6 = rddots(3);
%% write a central-difference method with respect to the six state variables
% gradient in the first component function F1
J11 = ( F1( (xG+h), yG, theta, vGx, vGy, omega ) - F1( (xG-h), yG, theta, vGx, vGy, omega ) ) / 2*h;
J12 = ( F1( xG, (yG+h), theta, vGx, vGy, omega ) - F1( xG, (yG -h), theta, vGx, vGy, omega ) ) / 2*h;
J13 = ( F1( xG, yG, (theta+h), vGx, vGy, omega ) - F1( xG, yG, (theta-h), vGx, vGy, omega ) ) / 2*h;
J14 = ( F1( xG, yG, theta, (vGx+h), vGy, omega ) - F1( xG, yG, theta, (vGx-h), vGy, omega ) ) / 2*h;
J15 = ( F1( xG, yG, theta, vGx, (vGy+h), omega ) - F1( xG, yG, theta, vGx, (vGy-h), omega ) ) / 2*h;
J16 = ( F1( xG, yG, theta, vGx, vGy, (omega+h) ) - F1( xG, yG, theta, vGx, vGy, (omega-h) ) ) / 2*h;
% gradient in the second component function F2
J21 = ( F2( (xG+h), yG, theta, vGx, vGy, omega ) - F2( (xG-h), yG, theta, vGx, vGy, omega ) ) / 2*h;
J22 = ( F2( xG, (yG+h), theta, vGx, vGy, omega ) - F2( xG, (yG -h), theta, vGx, vGy, omega ) ) / 2*h;
J23 = ( F2( xG, yG, (theta+h), vGx, vGy, omega ) - F2( xG, yG, (theta-h), vGx, vGy, omega ) ) / 2*h;
J24 = ( F2( xG, yG, theta, (vGx+h), vGy, omega ) - F2( xG, yG, theta, (vGx-h), vGy, omega ) ) / 2*h;
J25 = ( F2( xG, yG, theta, vGx, (vGy+h), omega ) - F2( xG, yG, theta, vGx, (vGy-h), omega ) ) / 2*h;
J26 = ( F2( xG, yG, theta, vGx, vGy, (omega+h) ) - F2( xG, yG, theta, vGx, vGy, (omega-h) ) ) / 2*h;
% gradient in the third component function F3
J31 = ( F3( (xG+h), yG, theta, vGx, vGy, omega ) - F3( (xG-h), yG, theta, vGx, vGy, omega ) ) / 2*h;
J32 = ( F3( xG, (yG+h), theta, vGx, vGy, omega ) - F3( xG, (yG -h), theta, vGx, vGy, omega ) ) / 2*h;
J33 = ( F3( xG, yG, (theta+h), vGx, vGy, omega ) - F3( xG, yG, (theta-h), vGx, vGy, omega ) ) / 2*h;
J34 = ( F3( xG, yG, theta, (vGx+h), vGy, omega ) - F3( xG, yG, theta, (vGx-h), vGy, omega ) ) / 2*h;
J35 = ( F3( xG, yG, theta, vGx, (vGy+h), omega ) - F3( xG, yG, theta, vGx, (vGy-h), omega ) ) / 2*h;
J36 = ( F3( xG, yG, theta, vGx, vGy, (omega+h) ) - F3( xG, yG, theta, vGx, vGy, (omega-h) ) ) / 2*h;
% gradient in the fourth component function F4
J41 = ( F4( (xG+h), yG, theta, vGx, vGy, omega ) - F4( (xG-h), yG, theta, vGx, vGy, omega ) ) / 2*h;
J42 = ( F4( xG, (yG+h), theta, vGx, vGy, omega ) - F4( xG, (yG -h), theta, vGx, vGy, omega ) ) / 2*h;
J43 = ( F4( xG, yG, (theta+h), vGx, vGy, omega ) - F4( xG, yG, (theta-h), vGx, vGy, omega ) ) / 2*h;
J44 = ( F4( xG, yG, theta, (vGx+h), vGy, omega ) - F4( xG, yG, theta, (vGx-h), vGy, omega ) ) / 2*h;
J45 = ( F4( xG, yG, theta, vGx, (vGy+h), omega ) - F4( xG, yG, theta, vGx, (vGy-h), omega ) ) / 2*h;
J46 = ( F4( xG, yG, theta, vGx, vGy, (omega+h) ) - F4( xG, yG, theta, vGx, vGy, (omega-h) ) ) / 2*h;
% gradient in the fifth component function F5
J51 = ( F5( (xG+h), yG, theta, vGx, vGy, omega ) - F5( (xG-h), yG, theta, vGx, vGy, omega ) ) / 2*h;
J52 = ( F5( xG, (yG+h), theta, vGx, vGy, omega ) - F5( xG, (yG -h), theta, vGx, vGy, omega ) ) / 2*h;
J53 = ( F5( xG, yG, (theta+h), vGx, vGy, omega ) - F5( xG, yG, (theta-h), vGx, vGy, omega ) ) / 2*h;
J54 = ( F5( xG, yG, theta, (vGx+h), vGy, omega ) - F5( xG, yG, theta, (vGx-h), vGy, omega ) ) / 2*h;
J55 = ( F5( xG, yG, theta, vGx, (vGy+h), omega ) - F5( xG, yG, theta, vGx, (vGy-h), omega ) ) / 2*h;
J56 = ( F5( xG, yG, theta, vGx, vGy, (omega+h) ) - F5( xG, yG, theta, vGx, vGy, (omega-h) ) ) / 2*h;
% gradient in the sixth component function F6
J61 = ( F6( (xG+h), yG, theta, vGx, vGy, omega ) - F6( (xG-h), yG, theta, vGx, vGy, omega ) ) / 2*h;
J62 = ( F6( xG, (yG+h), theta, vGx, vGy, omega ) - F6( xG, (yG -h), theta, vGx, vGy, omega ) ) / 2*h;
J63 = ( F6( xG, yG, (theta+h), vGx, vGy, omega ) - F6( xG, yG, (theta-h), vGx, vGy, omega ) ) / 2*h;
J64 = ( F6( xG, yG, theta, (vGx+h), vGy, omega ) - F6( xG, yG, theta, (vGx-h), vGy, omega ) ) / 2*h;
J65 = ( F6( xG, yG, theta, vGx, (vGy+h), omega ) - F6( xG, yG, theta, vGx, (vGy-h), omega ) ) / 2*h;
J66 = ( F6( xG, yG, theta, vGx, vGy, (omega+h) ) - F6( xG, yG, theta, vGx, vGy, (omega-h) ) ) / 2*h;
% Form the 6x6 Jacobian
J = [ J11, J12, J13, J14, J15, J16;
J21, J22, J23, J24, J25, J26;
J31, J32, J33, J34, J35, J36;
J41, J42, J43, J44, J45, J46;
J51, J52, J53, J54, J55, J56;
J61, J62, J63, J64, J65, J66 ];
  2 Comments
Noob
Noob on 29 Apr 2025
Edited: Noob on 29 Apr 2025
Hi John!
What's the best way for me to learn to write my own central-difference code? I've practiced a bit with simple functions such as x^2 and x^2 + y and x + y, and have no issues with toy problems such as those. I played with the step-size h, too, and noticed that too small a step-size gives the incorrect derivative. Is that very popular Jacobest / derivest code on the File Exchange your code? I see your name on it. If so, should I take a peek at that, and see how you wrote it? Or should I try something else first?
The Matlab syntax is the thing that is difficult here, I think.
Ideally, I'd love to write my own method, rather than use something from the File Exchange.
Thanks!
John D'Errico
John D'Errico on 29 Apr 2025
I moved my comment to an answer, and there I show how I would write a finite difference Jacobian. There are many ways to do it of course. (Yes, Jacobest is mine. But you want to learn to write your own. And jacobest does some tricky stuff with adaptive estimation of the derivatives. Those tools are all about adapting the finite difference to the function itself. It works nicely, but, tricky, and that would just be confusing.)

Sign in to comment.

Accepted Answer

Matt J
Matt J on 29 Apr 2025
Edited: Matt J on 29 Apr 2025
I'm assuming rdots and rddots are symbolic functions, based on your previous posts and on the fact that you have concatenated them together as vectors (which can only be done if the functions are symbolic). Assuming this, I would do,
F=matlabFunction([rdots;rddots], 'vars',{{xG, yG, theta, vGx, vGy, omega}});
function J = numericalJacobian(F,X,h)
arguments
F
X (6,1) double %numerical values for [xG; yG; theta; vGx; vGy; omega]
h=1e-5; %finite differencing step size
end
Delta=speye(6);
J=nan(6);
for i=1:6
J(:,i)=( F(X+h*Delta(:,i)) - F(X-h*Delta(:,i)) )/(2*h); %central difference calcs
end
end
  32 Comments
Torsten
Torsten on 1 May 2025
Would that exercise be utterly pointless?
I don't understand why you make things that complicated. Use the functions for your ODE system that you took as input to ode45 and apply the central-differencing code from above to get the Jacobians in your fixed points.
Noob
Noob on 1 May 2025
Edited: Noob on 1 May 2025
Yes, I've done that now and think I have a stable fixed point.
The hope was due to the small chance of getting a manageable-looking symbolic Jacobian, to see if I could learn anything from it. That chance is slim-to-none, I think -- unfortunately.
Goodnight, talk soon!
Thanks for all of your help!

Sign in to comment.

More Answers (2)

John D'Errico
John D'Errico on 29 Apr 2025
Edited: John D'Errico on 29 Apr 2025
What do we think of it? I'm sorry, but blech!
Why? Don't number all of your variables! Don't create long lists of numbered variables, then build a matrix like that. SHIVER. Blech squared. Hey, you asked.
Instead, learn to use arrays. Even loops. The code you do show is not complete, so we can't really know exactly what you have done, nor can we change your code in any easy way to show you how to improve it. For example, it seems that vGx, vGy, etc., are functions.
Think about how nasty code like yours would get when you actually have a problem of any serious size? An actual, real problem? As well, using those numbered variables will be hell to debug. It is easy to mistype something, and then it can be difficult to see.
How would I want to do this?
You have 3 variables. Keep them in a vector. I'll call it Vars. Inside the function, if it is an m-file function, you can unpack the vector into named variables, if that will make your code easier to read for you.
You have three functions. Create a vector of functions. I'll make up some functions.
Funs = {@(X) X(1)^2 + X(2)^2 + X(3)^2;...
@(X) sin(X(1));...
@(X) cos(X(1) + X(3))};
Next, I'll create a delta vector, allowing me to perturb any of the variables, by some small amount.
nvars = 3;
perturb = @(n,incr) ((1:nvars) == n)*incr;
So the perturb function creates a vector of length nvars, which is zero at all positions except for the indicated one. For example. Perturb the third variable, by an increment of 0.001. It can handle a negative increment.
perturb(3,0.001)
ans = 1×3
1.0e-03 * 0 0 1.0000
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
Now, create a Jacobian matrix.
nfuns = 3;
Jac = zeros(nfuns,nvars);
incr = 0.001;
X0 = [1 2 4]; % point around which we will create the jacobian matrix
for ifun = 1:nfuns
for ivar = 1:nvars
Jac(ifun,ivar) = (Funs{ifun} (X0 + perturb(ivar,incr)) - Funs{ifun} (X0 + perturb(ivar,-incr)))/(2*incr);
end
end
Jac
Jac = 3×3
2.0000 4.0000 8.0000 0.5403 0 0 0.9589 0 0.9589
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
The code is fairly simple. It is easy to see what was done. If there is a problem in the code, you need only verify ONE line of code. Everything else is obvious. And even that line of code is easy to read, since I created the perturb function, which makes it clear how it works.
Finally, I should check, is that a good estimate of the Jacobian matrix? This is an important step always. Verify that your code works. Don't just trust that it looks right. Everytime I do that, I'm wrong. (I could tell some painful auto-biographical stories...) But that means you should always check your code as you go along, one code fragment at a time. Then when you put it all together, it works.
x = sym('x',[1,3]);
funsyms = [subs(Funs{1} (x));subs(Funs{2} (x));subs(Funs{3} (x))];
fjac = jacobian(funsyms,x)
fjac = 
vpa(subs(fjac,x,X0),5)
ans = 
And that seems perfect. Again, the trick is to get used to working with vectors. Avoid those lists of numbered variables. That is a place you don't want your code to be going. Just imagine what your code would look like if you had a hundred variables.
Better code of course, would be a function, that would take a vector of functions, an increment, and a point to evaluate those derivatives at. I might do that by just encapsulating the code I wrote above into a single function. Could I have written it without the loops? Surely yes. But the loops are easy to read and write. Loops are not a terrible thing in MATLAB. And code that lacks loops might be more complicated, harder to read, harder to debug.
  2 Comments
Noob
Noob on 30 Apr 2025
Hi John!
Thanks so much for taking the time and care to write this answer!
I was told to use your method, eventually, but that for now, it would be good for me to write my own central-difference method.
I look forward to using your code!
Thanks again for your help!
Noob
Noob on 1 May 2025

Hi John!

As of tonight, I was able to write my own central-difference method! Now I know four ways to do it: Matt J’s, Torsten’s, my own, and I will soon get to yours! I notice two things:

1. The numerical Jacobian agrees with the exact Jacobian for up to 6, 7, or maybe 8 digits after the decimal point. Presumably, your Jacobianest code is more accurate.

2. Using a smaller step-size doesn’t necessarily mean that the numerical Jacobian agrees better with the exact Jacobian.

So, those are my two observations for now.

Just wanted to say thanks again for your time and help!

Goodnight!

Sign in to comment.


Torsten
Torsten on 29 Apr 2025
Moved: Torsten on 29 Apr 2025
It cannot be correct because F1,...,F6 are scalars, not functions.
And if F1,...,F6 were functions, the 2*h expression must be bracketed when calculating the Jacobian elements:
/ (2*h)
instead of
/ 2*h
  1 Comment
Noob
Noob on 29 Apr 2025
Hi Torsten!
Yes, I also have indexing errors.
For an array, F1( ... ) would index into F1; I mistakenly used ( ) to supply input arguments.
What do you think is good way for me to learn to write my own central-difference method in Matlab?
For instance, is there a good code online that I can glance at, to have an idea of how I should write my own code?
Thanks!

Sign in to comment.

Products


Release

R2024b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!