Looking for linear system Ax=0 precision improvements

29 views (last 30 days)
vanni meier
vanni meier on 27 Feb 2025
Commented: Paul on 30 Mar 2025 at 14:10
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
vanni meier
vanni meier on 6 Mar 2025
Combining everything so far, the accurate way to solve this is:
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)
Thank you to everybody for the solutions and sugesstions
Paul
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)
ans = 6.8898e-20
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
mineig = 
5.8980671129533164155466150759502e-42
x = double(V(:,index));
x = x./s';
x = x/sum(x); % my normalisation criterion
x = double(x);
norm(A*x)
ans = 4.7959e-20
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
mineig = 
5.8980671129533164155466150759502e-42
x = (V(:,index));
x = x./vpa(s,128)';
x = x/sum(x); % my normalisation criterion
x = double(x);
norm(A*x)
ans = 1.0077e-19
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)
ans = 4.6937e-20

Sign in to comment.

Answers (3)

David Goodmanson
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.

Mike Croucher
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
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)
vanni meier
vanni meier on 6 Mar 2025
Edited: vanni meier on 6 Mar 2025
Ok so i'm not familiar with vpa. if i do this, I have an empty nullspace. This comes from the fac that my matrix elements are not exact either:
A = vpa(A,128)
A(1:n+1:n*n)=zeros(1,n);
A(1:n+1:n*n)=-sum(A); % this step introduces roufoff
But when I solve it, the nullspace is empty due to the roundoff error. And i cannot add a tolerance to symbolic null:
>> null(A,1e-25)
Error using sym/null
Too many input arguments.
This also fails:
>> null(vpa(A,64))
Empty sym: 30-by-0

Sign in to comment.


Matt J
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
vanni meier
vanni meier on 6 Mar 2025
I am not very smart, i just have to take the first eigenvector.
David Goodmanson
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.

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!