Looking for linear system Ax=0 precision improvements
29 views (last 30 days)
Show older comments
Hi, I'm solving a system of equations
.

x=null(A);
The problem is that due to a very wide range of values in the A matrix (
) the roundoff error result in inacceptably large relative errors in the solution. How can I improve this numerical resolution? The matrix is relatively small (30x30) so speed is not a big limitation. Also all the large values are on the same colums.

Edit2: clarifying the question and objectives
To clarify my issue, my understanding is that null sort of ends up minimising
.

Its limited double precision and even if you plug in the exact solution, i can only hope to have about
A*x <= length(x)*eps(max(A.*x'))
When i plot A*x, I get something closer to
A*x = length(x)*eps(max(A.*x',[],'all'))
This is probably to be expected as minimisation of
"distributes the error" over all components equally?

In my case, I care about relative errors of individual coordinates . I want them to be small and at least know when a value is smaller than engine precision.
Can I be more precise in calculating the small value at the cost of the larger ones if necessary, and how up to what threshold I can trust this value?
A good improvement was proposed:
s = max(abs(A),[],1); %rescaling
x = null(A./s)./(s');
This is better but not quite sufficient and I stil miss the key aspect of knowing what value
is below reasonaly accurate. I initially used x>eps(1) to check for that. This was wrong but my following attemps were not any better.

----- Edit: Correcting the question following Matt's advice. -----
I benchmark the relative error over each element of the solution
, with
.


relative_error = abs((A*x)./(vecnorm(A)'.*x));
I have a kinda dirty solution using a combination of rescaling, null(), and fsolve(). You can see below the result for four different approaches.

x1 = null(A);
x1 = x1/sum(x1(:,1));
s = max(abs(A),[],1); %rescaling
x2 = null(A./s)./(s');
x2 = x2/sum(x2(:,1));
x3 = fsolve(@(y) A*y,x2,optimoptions('fsolve','Display','off'));
x3=x3/sum(x3);
precision = length(A)*eps(max(abs(A.*(x2')),[],2));
x4 = fsolve(@(y) A*y./max(abs(x2),precision),x2,optimoptions('fsolve','Display','off'));
x4 = x4/sum(x4);
figure
hold on
plot(x4,'k--')
relative_error = abs((A*x1)./(vecnorm(A)'.*x1));
plot(relative_error)
relative_error = abs((A*x2)./(vecnorm(A)'.*x2));
plot(relative_error)
relative_error = abs((A*x3)./(vecnorm(A)'.*x3));
plot(relative_error)
relative_error = abs((A*x4)./(vecnorm(A)'.*x4));
plot(relative_error)
set(gca,'Yscale','log')
legend({'x_4','err 1','err 2','err 3','err 4'})
The method is probably a bit slow and dirty but the relative error reached is close to eps.
6 Comments
Paul
on 30 Mar 2025 at 14:10
Hi vanni,
A few thoughts to consider.
Here is the original code:
load('A.mat');
n=size(A,1);
s=-A(1:n+1:n*n);
B=vpa(A./s,128);
B(1:n+1:n*n)=zeros(1,n);
B(1:n+1:n*n)=-sum(B);
[V,D]=eig(B);
x = double(V(:,1));
x = x./s';
x = x/sum(x); % my normalisation criterion
x = double(x);
norm(A*x)
The code assumes that the first eigenvalue returned by eig is the smallest, but I don't see anything on that doc page that states that as a fact. So it might be more robust to actually find which eigenvalue is the smallest.
If going the VPA route, it's probably better to convert A and s to VPA and then divide
B = vpa(A,128)./vpa(s,128);
B(1:n+1:n*n)=zeros(1,n);
B(1:n+1:n*n)=-sum(B);
[V,D]=eig(B);
[mineig,index] = min(abs(diag(D)));
mineig
x = double(V(:,index));
x = x./s';
x = x/sum(x); % my normalisation criterion
x = double(x);
norm(A*x)
I noticed that the code converts x back to double immediately after the call to eig, and thought further improvement might be gained by keeping things in VPA until the end.
B = vpa(A,128)./vpa(s,128);
B(1:n+1:n*n)=zeros(1,n);
B(1:n+1:n*n)=-sum(B);
[V,D]=eig(B);
[mineig,index] = min(abs(diag(D)));
mineig
x = (V(:,index));
x = x./vpa(s,128)';
x = x/sum(x); % my normalisation criterion
x = double(x);
norm(A*x)
Alas, that's not the case. But maybe that's because the diagonal of B isn't really the diagonal of A. If we force the diagonal of A to meet the same constraints as B, then
AA = A;
AA(1:n+1:n*n) = 0;
AA(1:n+1:n*n) = -sum(AA);
norm(AA*x)
Answers (3)
David Goodmanson
on 7 Mar 2025
Edited: David Goodmanson
on 30 Mar 2025 at 4:56
Hi vanni,
My comment of March 4 discussed relative error calculations in solving for x in A*x = 0. (Some boring details are mentioned there but not here)
You mentioned that column elements of A tend to have the same size, meaning that scaling the columns of A can help increase the precision.
In the comment, a proposed solution is based on the fact that for A*x = z, the nth element of z is found by multiplying the nth row of A by x. For each row vector Arow, Arow*x should be zero, i.e. every Arow is orthogonal to x. A common expression for orthogonality relative error is
(Arow*x)/(norm(Arow)*norm(x)) % row norms of A.
Orthogonality and scaling lead to the following code, which assumes row norms only. The process works (see notes below).
% unscaled
xa = null(A);
xa = xa/norm(xa);
rela = (A*xa)./(vecnorm(A,2,2)*norm(xa)); % relative error w/ row norms
% use scaling
s = max(abs(A )); % max by column
xbs = null(A./s); % scaled null vector
xb = xbs./(s'); % scale back
xb = xb/norm(xb);
relb = (A*xb)./(vecnorm(A,2,2)*norm(xb)); % relative error w/ row norms
You posted three A matrices all together,one on the day after you posted the question (I will call this A1) and two more, which I think are identical, in the last day or so (A2). Both matrices have all negative values on the diagonal, and all positive values (or zeros) off the diagonal. So I imagine you could make a good case that the x you are solving for should have all positive entries (not counting that you could mutiply x by an overall minus sign and still have a solution).
Sometimes the calculated xa (unscaled A) or xb (scaled A) have negative values. The only bad case would occur when xa or xb is negative but significantly larger in absolute value than its error estimate.
You have said that that bad cases are happening for A2, but sticking to the code above I am not finding any bad cases for the scaled version xb, although there are bad cases for the unscaled version xa. (xa and xb needed to be multiplied by an overall minus sign in order to get them into the right form).
Matrix A1 comes up with one negative value for xb but it is not a bad case.
various comments:
oo For Ax = 0, the normalization of x does not matter, so for consistency it makes sense to use normalized x, x = x/norm(x). In all these cases the first element of x is very close to 1, and the rest of the elements are small to very small.
oo Looking for max accuracy with the posted A matrices could be difficult, because for all I know they may have been calculated to 15 significant figures, but the shipping process came up with 5 significant figures on this site.
oo This problem is not set up for great numerical success since the condition numbers of A1 and A2 are 1.3067e+21 and 1.7792e+22 respectively.
oo If you mentioned that the columns of A are intended to sum to 1, I missed it.
oo The comment employed column norms as well as row norms, with the idea of showing that column norms were unwanted.
0 Comments
Mike Croucher
on 27 Feb 2025
I'm not an expert in this type of thing so take the following with caution but perhaps try vpa to give more precision? Something like
vpaA = vpa(a,128);
x = null(vpaA)
2 Comments
Walter Roberson
on 27 Feb 2025
I would not recommend this approach. I would suggest instead
sA = sym(A);
x = null(sA);
possibly followed by double(x) or vpa(x)
Matt J
on 27 Feb 2025
Edited: Matt J
on 27 Feb 2025
s=max(abs(A),[],1);
x = null(A./s)./(s')
18 Comments
David Goodmanson
on 7 Mar 2025
Edited: David Goodmanson
on 8 Mar 2025
Hi vanni,
After taking a look at this, I feel good enough about the method to post the material as an answer.
See Also
Categories
Find more on Fixed-Point Matrix Operations in Simulink in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!