Skip to content

Commit 42b28a4

Browse files
fix(linalg): use column-major loops and harden input validation in weighted/generalized lstsq
1 parent 2065886 commit 42b28a4

1 file changed

Lines changed: 76 additions & 20 deletions

File tree

src/linalg/stdlib_linalg_least_squares.fypp

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -590,9 +590,11 @@ submodule (stdlib_linalg) stdlib_linalg_least_squares
590590

591591
! Local variables
592592
type(linalg_state_type) :: err0
593-
integer(ilp) :: m, n, i
593+
integer(ilp) :: m, n, j
594594
logical(lk) :: copy_a
595-
${rt}$, allocatable :: a_scaled(:,:), b_scaled(:)
595+
${rt}$, pointer :: amat(:,:)
596+
${rt}$, allocatable, target :: amat_alloc(:,:)
597+
${rt}$, allocatable :: b_scaled(:)
596598
real(${rk}$), allocatable :: sqrt_w(:)
597599
character(*), parameter :: this = 'weighted_lstsq'
598600

@@ -602,6 +604,14 @@ submodule (stdlib_linalg) stdlib_linalg_least_squares
602604
! Allocate result (even on error, to prevent segfault on return)
603605
allocate(x(n))
604606

607+
! Validate matrix dimensions
608+
if (m < 1 .or. n < 1) then
609+
err0 = linalg_state_type(this, LINALG_VALUE_ERROR, 'Invalid matrix size a(m, n) =', [m, n])
610+
call linalg_error_handling(err0, err)
611+
if (present(rank)) rank = 0
612+
return
613+
end if
614+
605615
! Validate inputs
606616
if (size(w, kind=ilp) /= m) then
607617
err0 = linalg_state_type(this, LINALG_VALUE_ERROR, &
@@ -611,6 +621,14 @@ submodule (stdlib_linalg) stdlib_linalg_least_squares
611621
return
612622
end if
613623

624+
if (size(b, kind=ilp) /= m) then
625+
err0 = linalg_state_type(this, LINALG_VALUE_ERROR, &
626+
'Right-hand side size must match rows of A:', size(b, kind=ilp), '/=', m)
627+
call linalg_error_handling(err0, err)
628+
if (present(rank)) rank = 0
629+
return
630+
end if
631+
614632
if (any(w <= 0.0_${rk}$)) then
615633
err0 = linalg_state_type(this, LINALG_VALUE_ERROR, 'Weights must be positive')
616634
call linalg_error_handling(err0, err)
@@ -629,28 +647,29 @@ submodule (stdlib_linalg) stdlib_linalg_least_squares
629647
allocate(sqrt_w(m))
630648
sqrt_w = sqrt(w)
631649

632-
! Scale A and b
650+
! Handle A matrix: either copy or use original
633651
if (copy_a) then
634-
! Allocate and scale into copy (preserves original A)
635-
allocate(a_scaled(m, n))
636-
do i = 1, m
637-
a_scaled(i, :) = sqrt_w(i) * a(i, :)
638-
end do
652+
allocate(amat_alloc(m, n))
653+
amat => amat_alloc
639654
else
640-
! Scale A in-place (destroys original A)
641-
allocate(a_scaled(m, n))
642-
do i = 1, m
643-
a_scaled(i, :) = sqrt_w(i) * a(i, :)
644-
a(i, :) = a_scaled(i, :)
645-
end do
655+
amat => a
646656
end if
647657

658+
! Scale A column-wise (cache-friendly: column-major order)
659+
do j = 1, n
660+
amat(:, j) = sqrt_w(:) * a(:, j)
661+
end do
662+
648663
! Scale b
649664
allocate(b_scaled(m))
650665
b_scaled = sqrt_w * b
651666

652667
! Solve transformed OLS problem
653-
x = stdlib_linalg_${ri}$_lstsq_one(a_scaled, b_scaled, cond=cond, overwrite_a=.true., rank=rank, err=err)
668+
x = stdlib_linalg_${ri}$_lstsq_one(amat, b_scaled, cond=cond, overwrite_a=.true., rank=rank, err=err)
669+
670+
! Cleanup
671+
if (copy_a) deallocate(amat_alloc)
672+
deallocate(b_scaled, sqrt_w)
654673

655674
end function stdlib_linalg_${ri}$_weighted_lstsq
656675
#:endfor
@@ -679,7 +698,7 @@ submodule (stdlib_linalg) stdlib_linalg_least_squares
679698

680699
! Local variables
681700
type(linalg_state_type) :: err0
682-
integer(ilp) :: m, n, p, lda, ldb, lwork, info
701+
integer(ilp) :: m, n, p, lda, ldb, lwork, info, i, j
683702
logical(lk) :: copy_a, is_prefactored
684703
${rt}$, pointer :: amat(:,:)
685704
${rt}$, allocatable, target :: amat_alloc(:,:)
@@ -690,6 +709,16 @@ submodule (stdlib_linalg) stdlib_linalg_least_squares
690709
n = size(a, 2, kind=ilp)
691710
p = m ! For GLS, B is m×m
692711

712+
! Allocate result early (prevents segfault on error return)
713+
allocate(x(n))
714+
715+
! Validate matrix dimensions
716+
if (m < 1 .or. n < 1) then
717+
err0 = linalg_state_type(this, LINALG_VALUE_ERROR, 'Invalid matrix size a(m, n) =', [m, n])
718+
call linalg_error_handling(err0, err)
719+
return
720+
end if
721+
693722
! Validate sizes
694723
if (size(w, 1, kind=ilp) /= m .or. size(w, 2, kind=ilp) /= m) then
695724
err0 = linalg_state_type(this, LINALG_VALUE_ERROR, &
@@ -698,6 +727,20 @@ submodule (stdlib_linalg) stdlib_linalg_least_squares
698727
return
699728
end if
700729

730+
if (size(b, kind=ilp) /= m) then
731+
err0 = linalg_state_type(this, LINALG_VALUE_ERROR, &
732+
'Right-hand side size must match rows of A:', size(b, kind=ilp), '/=', m)
733+
call linalg_error_handling(err0, err)
734+
return
735+
end if
736+
737+
if (m < n) then
738+
err0 = linalg_state_type(this, LINALG_VALUE_ERROR, &
739+
'GGGLM requires m >= n (overdetermined or square):', m, '<', n)
740+
call linalg_error_handling(err0, err)
741+
return
742+
end if
743+
701744
! Process options
702745
is_prefactored = .false._lk
703746
if (present(prefactored_w)) is_prefactored = prefactored_w
@@ -708,9 +751,6 @@ submodule (stdlib_linalg) stdlib_linalg_least_squares
708751
copy_a = .true._lk
709752
end if
710753

711-
! Allocate result
712-
allocate(x(n))
713-
714754
! Handle A matrix
715755
if (copy_a) then
716756
allocate(amat_alloc(m, n), source=a)
@@ -734,6 +774,9 @@ submodule (stdlib_linalg) stdlib_linalg_least_squares
734774
err0 = linalg_state_type(this, LINALG_VALUE_ERROR, &
735775
'Invalid argument to POTRF at position', -info)
736776
end if
777+
! Cleanup before early return
778+
if (copy_a) deallocate(amat_alloc)
779+
deallocate(lmat)
737780
call linalg_error_handling(err0, err)
738781
return
739782
end if
@@ -746,10 +789,18 @@ submodule (stdlib_linalg) stdlib_linalg_least_squares
746789
lda = m
747790
ldb = m
748791

792+
! Zero out upper triangle of lmat (GGGLM reads full matrix,
793+
! but potrf only sets lower triangle)
794+
do j = 1, m
795+
do i = 1, j - 1
796+
lmat(i, j) = 0.0_${rk}$
797+
end do
798+
end do
799+
749800
! Workspace query
750801
allocate(work(1))
751802
call ggglm(m, n, p, amat, lda, lmat, ldb, d, x, y, work, -1_ilp, info)
752-
lwork = int(real(work(1), ${rk}$), ilp)
803+
lwork = ceiling(real(work(1), kind=${rk}$), kind=ilp)
753804
deallocate(work)
754805
allocate(work(lwork))
755806

@@ -758,6 +809,11 @@ submodule (stdlib_linalg) stdlib_linalg_least_squares
758809

759810
! Handle errors
760811
call handle_ggglm_info(this, info, m, n, p, err0)
812+
813+
! Cleanup
814+
if (copy_a) deallocate(amat_alloc)
815+
deallocate(lmat, d, y, work)
816+
761817
call linalg_error_handling(err0, err)
762818

763819
end function stdlib_linalg_${ri}$_generalized_lstsq

0 commit comments

Comments
 (0)