Skip to content

Commit 33559f5

Browse files
authored
Merge pull request #801 from perazz/least_squares
linalg: least squares
2 parents 3a2d503 + 2a1a88a commit 33559f5

10 files changed

+836
-6
lines changed

doc/specs/stdlib_linalg.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,130 @@ Specifically, upper Hessenberg matrices satisfy `a_ij = 0` when `j < i-1`, and l
600600
{!example/linalg/example_is_hessenberg.f90!}
601601
```
602602

603+
## `lstsq` - Computes the least squares solution to a linear matrix equation.
604+
605+
### Status
606+
607+
Experimental
608+
609+
### Description
610+
611+
This function computes the least-squares solution to a linear matrix equation \( A \cdot x = b \).
612+
613+
Result vector `x` returns the approximate solution that minimizes the 2-norm \( || A \cdot x - b ||_2 \), i.e., it contains the least-squares solution to the problem. Matrix `A` may be full-rank, over-determined, or under-determined. The solver is based on LAPACK's `*GELSD` backends.
614+
615+
### Syntax
616+
617+
`x = ` [[stdlib_linalg(module):lstsq(interface)]] `(a, b, [, cond, overwrite_a, rank, err])`
618+
619+
### Arguments
620+
621+
`a`: Shall be a rank-2 `real` or `complex` array containing the coefficient matrix. It is an `intent(inout)` argument.
622+
623+
`b`: Shall be a rank-1 or rank-2 array of the same kind as `a`, containing one or more right-hand-side vector(s), each in its leading dimension. It is an `intent(in)` argument.
624+
625+
`cond` (optional): Shall be a scalar `real` value cut-off threshold for rank evaluation: `s_i >= cond*maxval(s), i=1:rank`. Shall be a scalar, `intent(in)` argument.
626+
627+
`overwrite_a` (optional): Shall be an input `logical` flag. If `.true.`, input matrix `A` will be used as temporary storage and overwritten. This avoids internal data allocation. This is an `intent(in)` argument.
628+
629+
`rank` (optional): Shall be an `integer` scalar value, that contains the rank of input matrix `A`. This is an `intent(out)` argument.
630+
631+
`err` (optional): Shall be a `type(linalg_state_type)` value. This is an `intent(out)` argument.
632+
633+
### Return value
634+
635+
Returns an array value of the same kind and rank as `b`, containing the solution(s) to the least squares system.
636+
637+
Raises `LINALG_ERROR` if the underlying Singular Value Decomposition process did not converge.
638+
Raises `LINALG_VALUE_ERROR` if the matrix and right-hand-side vector have invalid/incompatible sizes.
639+
Exceptions trigger an `error stop`.
640+
641+
### Example
642+
643+
```fortran
644+
{!example/linalg/example_lstsq1.f90!}
645+
```
646+
647+
## `solve_lstsq` - Compute the least squares solution to a linear matrix equation (subroutine interface).
648+
649+
### Status
650+
651+
Experimental
652+
653+
### Description
654+
655+
This subroutine computes the least-squares solution to a linear matrix equation \( A \cdot x = b \).
656+
657+
Result vector `x` returns the approximate solution that minimizes the 2-norm \( || A \cdot x - b ||_2 \), i.e., it contains the least-squares solution to the problem. Matrix `A` may be full-rank, over-determined, or under-determined. The solver is based on LAPACK's `*GELSD` backends.
658+
659+
### Syntax
660+
661+
`call ` [[stdlib_linalg(module):solve_lstsq(interface)]] `(a, b, x, [, real_storage, int_storage, [cmpl_storage, ] cond, singvals, overwrite_a, rank, err])`
662+
663+
### Arguments
664+
665+
`a`: Shall be a rank-2 `real` or `complex` array containing the coefficient matrix. It is an `intent(inout)` argument.
666+
667+
`b`: Shall be a rank-1 or rank-2 array of the same kind as `a`, containing one or more right-hand-side vector(s), each in its leading dimension. It is an `intent(in)` argument.
668+
669+
`x`: Shall be an array of same kind and rank as `b`, containing the solution(s) to the least squares system. It is an `intent(inout)` argument.
670+
671+
`real_storage` (optional): Shall be a `real` rank-1 array of the same kind `a`, providing working storage for the solver. It minimum size can be determined with a call to [[stdlib_linalg(module):lstsq_space(interface)]]. It is an `intent(inout)` argument.
672+
673+
`int_storage` (optional): Shall be an `integer` rank-1 array, providing working storage for the solver. It minimum size can be determined with a call to [[stdlib_linalg(module):lstsq_space(interface)]]. It is an `intent(inout)` argument.
674+
675+
`cmpl_storage` (optional): For `complex` systems, it shall be a `complex` rank-1 array, providing working storage for the solver. It minimum size can be determined with a call to [[stdlib_linalg(module):lstsq_space(interface)]]. It is an `intent(inout)` argument.
676+
677+
`cond` (optional): Shall be a scalar `real` value cut-off threshold for rank evaluation: `s_i >= cond*maxval(s), i=1:rank`. Shall be a scalar, `intent(in)` argument.
678+
679+
`singvals` (optional): Shall be a `real` rank-1 array of the same kind `a` and size at least `minval(shape(a))`, returning the list of singular values `s(i)>=cond*maxval(s)`, in descending order of magnitude. It is an `intent(out)` argument.
680+
681+
`overwrite_a` (optional): Shall be an input `logical` flag. If `.true.`, input matrix `A` will be used as temporary storage and overwritten. This avoids internal data allocation. This is an `intent(in)` argument.
682+
683+
`rank` (optional): Shall be an `integer` scalar value, that contains the rank of input matrix `A`. This is an `intent(out)` argument.
684+
685+
`err` (optional): Shall be a `type(linalg_state_type)` value. This is an `intent(out)` argument.
686+
687+
### Return value
688+
689+
Returns an array value that represents the solution to the least squares system.
690+
691+
Raises `LINALG_ERROR` if the underlying Singular Value Decomposition process did not converge.
692+
Raises `LINALG_VALUE_ERROR` if the matrix and right-hand-side vector have invalid/incompatible sizes.
693+
Exceptions trigger an `error stop`.
694+
695+
### Example
696+
697+
```fortran
698+
{!example/linalg/example_lstsq2.f90!}
699+
```
700+
701+
## `lstsq_space` - Compute internal working space requirements for the least squares solver.
702+
703+
### Status
704+
705+
Experimental
706+
707+
### Description
708+
709+
This subroutine computes the internal working space requirements for the least-squares solver, [[stdlib_linalg(module):solve_lstsq(interface)]] .
710+
711+
### Syntax
712+
713+
`call ` [[stdlib_linalg(module):lstsq_space(interface)]] `(a, b, lrwork, liwork [, lcwork])`
714+
715+
### Arguments
716+
717+
`a`: Shall be a rank-2 `real` or `complex` array containing the linear system coefficient matrix. It is an `intent(in)` argument.
718+
719+
`b`: Shall be a rank-1 or rank-2 array of the same kind as `a`, containing the system's right-hand-side vector(s). It is an `intent(in)` argument.
720+
721+
`lrwork`: Shall be an `integer` scalar, that returns the minimum array size required for the `real` working storage to this system.
722+
723+
`liwork`: Shall be an `integer` scalar, that returns the minimum array size required for the `integer` working storage to this system.
724+
725+
`lcwork` (`complex` `a`, `b`): For a `complex` system, shall be an `integer` scalar, that returns the minimum array size required for the `complex` working storage to this system.
726+
603727
## `det` - Computes the determinant of a square matrix
604728

605729
### Status

example/linalg/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ ADD_EXAMPLE(state1)
1818
ADD_EXAMPLE(state2)
1919
ADD_EXAMPLE(blas_gemv)
2020
ADD_EXAMPLE(lapack_getrf)
21+
ADD_EXAMPLE(lstsq1)
22+
ADD_EXAMPLE(lstsq2)
2123
ADD_EXAMPLE(determinant)
2224
ADD_EXAMPLE(determinant2)
23-

example/linalg/example_lstsq1.f90

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
! Least-squares solver: functional interface
2+
program example_lstsq1
3+
use stdlib_linalg_constants, only: dp
4+
use stdlib_linalg, only: lstsq
5+
implicit none
6+
7+
integer, allocatable :: x(:),y(:)
8+
real(dp), allocatable :: A(:,:),b(:),coef(:)
9+
10+
! Data set
11+
x = [1, 2, 2]
12+
y = [5, 13, 25]
13+
14+
! Fit three points using a parabola, least squares method
15+
! A = [1 x x**2]
16+
A = reshape([[1,1,1],x,x**2],[3,3])
17+
b = y
18+
19+
! Get coefficients of y = coef(1) + x*coef(2) + x^2*coef(3)
20+
coef = lstsq(A,b)
21+
22+
print *, 'parabola: ',coef
23+
! parabola: -0.42857142857141695 1.1428571428571503 4.2857142857142811
24+
25+
26+
end program example_lstsq1

example/linalg/example_lstsq2.f90

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
! Demonstrate expert subroutine interface with pre-allocated arrays
2+
program example_lstsq2
3+
use stdlib_linalg_constants, only: dp,ilp
4+
use stdlib_linalg, only: solve_lstsq, lstsq_space, linalg_state_type
5+
implicit none
6+
7+
integer, allocatable :: x(:),y(:)
8+
real(dp), allocatable :: A(:,:),b(:),coef(:),real_space(:),singvals(:)
9+
integer(ilp), allocatable :: int_space(:)
10+
integer(ilp) :: lrwork,liwork,arank
11+
12+
! Data set
13+
x = [1, 2, 2]
14+
y = [5, 13, 25]
15+
16+
! Fit three points using a parabola, least squares method
17+
! A = [1 x x**2]
18+
A = reshape([[1,1,1],x,x**2],[3,3])
19+
b = y
20+
21+
! Get storage sizes for the arrays and pre-allocate data
22+
call lstsq_space(A,b,lrwork,liwork)
23+
allocate(coef(size(x)),real_space(lrwork),int_space(liwork),singvals(minval(shape(A))))
24+
25+
! Solve coefficients of y = coef(1) + x*coef(2) + x^2*coef(3)
26+
! with no internal allocations
27+
call solve_lstsq(A,b,x=coef, &
28+
real_storage=real_space, &
29+
int_storage=int_space, &
30+
singvals=singvals, &
31+
overwrite_a=.true., &
32+
rank=arank)
33+
34+
print *, 'parabola: ',coef
35+
! parabola: -0.42857142857141695 1.1428571428571503 4.2857142857142811
36+
print *, 'rank: ',arank
37+
! rank: 2
38+
39+
40+
end program example_lstsq2

include/common.fypp

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,20 @@
2727
#:set REAL_KINDS = REAL_KINDS + ["qp"]
2828
#:endif
2929

30+
#! BLAS/LAPACK initials for each real kind
31+
#:set REAL_INIT = ["s", "d"]
32+
#:if WITH_XDP
33+
#:set REAL_INIT = REAL_INIT + ["x"]
34+
#:endif
35+
#:if WITH_QP
36+
#:set REAL_INIT = REAL_INIT + ["q"]
37+
#:endif
38+
3039
#! Real types to be considered during templating
3140
#:set REAL_TYPES = ["real({})".format(k) for k in REAL_KINDS]
3241

3342
#! Collected (kind, type) tuples for real types
34-
#:set REAL_KINDS_TYPES = list(zip(REAL_KINDS, REAL_TYPES))
43+
#:set REAL_KINDS_TYPES = list(zip(REAL_KINDS, REAL_TYPES, REAL_INIT))
3544

3645
#! Complex kinds to be considered during templating
3746
#:set CMPLX_KINDS = ["sp", "dp"]
@@ -42,11 +51,20 @@
4251
#:set CMPLX_KINDS = CMPLX_KINDS + ["qp"]
4352
#:endif
4453

54+
#! BLAS/LAPACK initials for each complex kind
55+
#:set CMPLX_INIT = ["c", "z"]
56+
#:if WITH_XDP
57+
#:set CMPLX_INIT = CMPLX_INIT + ["y"]
58+
#:endif
59+
#:if WITH_QP
60+
#:set CMPLX_INIT = CMPLX_INIT + ["w"]
61+
#:endif
62+
4563
#! Complex types to be considered during templating
4664
#:set CMPLX_TYPES = ["complex({})".format(k) for k in CMPLX_KINDS]
4765

48-
#! Collected (kind, type) tuples for complex types
49-
#:set CMPLX_KINDS_TYPES = list(zip(CMPLX_KINDS, CMPLX_TYPES))
66+
#! Collected (kind, type, initial) tuples for complex types
67+
#:set CMPLX_KINDS_TYPES = list(zip(CMPLX_KINDS, CMPLX_TYPES, CMPLX_INIT))
5068

5169
#! Integer kinds to be considered during templating
5270
#:set INT_KINDS = ["int8", "int16", "int32", "int64"]
@@ -109,6 +127,17 @@
109127
#{if rank > 0}#(${":" + ",:" * (rank - 1)}$)#{endif}#
110128
#:enddef
111129

130+
#! Generates an empty array rank suffix.
131+
#!
132+
#! Args:
133+
#! rank (int): Rank of the variable
134+
#!
135+
#! Returns:
136+
#! Empty array rank suffix string (e.g. (0,0) if rank = 2)
137+
#!
138+
#:def emptyranksuffix(rank)
139+
#{if rank > 0}#(${"0" + ",0" * (rank - 1)}$)#{endif}#
140+
#:enddef
112141

113142
#! Joins stripped lines with given character string
114143
#!

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ set(fppFiles
2121
stdlib_kinds.fypp
2222
stdlib_linalg.fypp
2323
stdlib_linalg_diag.fypp
24+
stdlib_linalg_least_squares.fypp
2425
stdlib_linalg_outer_product.fypp
2526
stdlib_linalg_kronecker.fypp
2627
stdlib_linalg_cross_product.fypp

0 commit comments

Comments
 (0)