I’ve been working on a non-negative least squares solver command, based on FNNLS algorithm. Although it does work, it has what I suspect is a precision-related problem - there are certain inputs which never converge, i.e. it loops forever. It’s also possibly just a straightforward script error.
I’m posting it here, because I’m about to run out of time on it for now and I don’t want to push a command which can infinite loop. The hope is somebody else might find a fix, or rewrite it!
# Non-negative least squares
# Solve Ax = b, xi>0 for selected A matrix and specified b vector
gcd_solve_nonnegative : check ${"is_image_arg $1"}
pass$1 0 => B
repeat $!-1 { l[$>,-1] {
M,N:=[h#0,w#0] act=$N
# Active and passive indexes are held in a single vector: [ind]
1,$N,1,1 => X . => S . => W +f. y => ind
+transpose[0] . mmul.. [0] mmul. [B] =>.. AtA => AtB
for $act>0 {
# W = AtB - AtA*x (negative gradient)
j[W] [AtB] +mmul[AtA] [X] sub[W,-1]
1,$act,1,1,"i[#$W,i[#$ind,y]]" => Wact
# EXIT if max (negative) gradient is close to zero
if iM<1e-6 rm[Wact] break fi
# Move index of max Wact element from active to passive list
eval "
a_index = yM;
w_index = i[#$ind,a_index];
copy(i[#$ind,a_index],i[#$ind,a_index+1],$N-1-a_index);
i[#$ind,$N-1] = w_index;"
rm[Wact] act:=$act-1
# Enforce non-negative constraint
for 1 {
# Solve As = b for current passive set
part:=$N-$act
sh[ind] $act,100%,0,0 => psv # passive indexes
$part,$part,1,1,"i(#$AtA,i[#$psv,x],i[#$psv,y])" => Apsv
1,$part,1,1,"i[#$AtB,i[#$psv,y]]" => Bpsv
+solve[Bpsv] [Apsv] => Spsv
# Copy passive solution to S, set to zero for active variables
eval[S] "i[i[#$ind,y]]=y<$act?0:i[#$Spsv,y-$act]"
rm[Apsv,Bpsv]
# EXIT moving negatives if passive solution is all positive
if im>0 rm[psv,Spsv] break fi
# Calculate possible scaling ratios
1,$part,1,1,"i[#$X,i[#$psv,y]]" => Xpsv
1,$part,1,1,"XP=i[#$Xpsv,y];XP/(XP-i[#$Spsv,y])" => ratio
# Find minimum ratio
eval[Spsv] "
begin(MR=inf);
MR=i>0?MR:min(MR,i[#$ratio,y]);
end(set('alpha',MR))"
rm[Xpsv,ratio]
# X = X + alpha*(S-X) (Do the scaling)
+sub[S] [X] mul. $alpha add[X,-1]
# Move indexes from passive to active where X[p_index] <= 0
eval[psv] "
begin(t=0);
p_index = i;
if(i[#$X,p_index]<=0,
copy(i[#$ind,1],i[#$ind,0],y+$act);
i[#$ind,0] = p_index;
t++;
);
end(set('act',$act+t))"
rm[psv,Spsv]
}
j[X] [S]
}
k[X]
} }
Example usage:
gmic 4,3,1,1,"round(u(10))" 1,3,1,1,"round(u(10))" +gcd_solve_nonnegative.. .
Example failure inputs (never ends):
gmic "(-250517.890625,224689,315384.71875,-272708.90625;-239055.84375,60966.41015625,174796.484375,-51424.0234375;63545.421875,-52172.0234375,-76025.2421875,62647.45703125)" "(1;1;1)" +gcd_solve_nonnegative.. .
The main differences to the usual FNNLS are:
- This uses a different loop format using
break
to avoid duplicated code. That’s normally considered bad (but this is my choice) - The active and passive indexes are held in a single image/vector. I don’t know if that’s a good method.