diff --git a/DESCRIPTION b/DESCRIPTION
index 95df1379604ed2819fbe45a6bd6a333dd9718259..85bb15c8271d0b3885f20ca2a271575793ec2575 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,11 +1,13 @@
 Package: gmresls
-Title: What the Package Does (One Line, Title Case)
-Version: 0.0.0.9000
+Title: Solve Least Squares with GMRES(k)
+Version: 0.1.0
 Authors@R: 
-    person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
-           comment = c(ORCID = "YOUR-ORCID-ID"))
-Description: What the package does (one paragraph).
+    person("Serguei", "Sokol", , "sokol@insa-toulouse.fr", role = c("aut", "cre"),
+           comment = c(ORCID = "0000-0002-5674-3327"))
+Description: Solves a least squares system Ax=b (dim(A)=(m,n) with m >= n) with a preconditioner B: BAx=Bb (dim(B)=(n,m)). Implemented method uses GMRES(k) with callback functions, i.e. no explicit A or B are required. GMRES can be restarted after k iterations.
 License: GPL (>= 3)
 Encoding: UTF-8
 Roxygen: list(markdown = TRUE)
 RoxygenNote: 7.3.1
+Suggests: 
+    RUnit
diff --git a/NAMESPACE b/NAMESPACE
index 6ae926839dd1829f1016a96f766d970ff184ad97..8396a214fd9faf09ab42ad04af3e4dc7fd651bcb 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -1,2 +1,3 @@
 # Generated by roxygen2: do not edit by hand
 
+export(gmresls)
diff --git a/R/gmresls.R b/R/gmresls.R
new file mode 100644
index 0000000000000000000000000000000000000000..3f28766b6a8d039f14d46ab45002399159629064
--- /dev/null
+++ b/R/gmresls.R
@@ -0,0 +1,94 @@
+#' Solve a Least Squares System with a Preconditioner.
+#'
+#' Solve a least squares system Ax=b (dim(A)=(m,n) with m >= n) with a
+#' preconditioner B: BAx=Bb (dim(B)=(n,m)).
+#' Implemented method uses GMRES(k) with callback functions, i.e. no explicit A or B are required. GMRES can be restarted after k iterations.
+#'
+#' @param f_Ax A function calculating matrix-vector product Ax for a given x
+#' @param f_Br A function calculating matrix-vector product Br for a given r
+#' @param b A vector of right hand side
+#' @param x0 A vector or NULL (which means 0), initial approximation for Ax=b
+#' @param k An integer, parameter for restarting GMRES. Value 0 (default) means no restart, i.e. at most length(x) basis vectors will be constructed and used.
+#' @param maxit A maximal iteration number. Default (0) means length(x).
+#' @param tol A tolerance for solution, estimated as ||B(Ax-b)||/||Bb||, default 1.e-7
+#' @param ... Parameters passed through to f_Ax and f_Br
+#' @returns The solution x, having the structure of Bb.
+#' @details
+#' Implemented method is equivalent to a classical GMRES(k) method with restart after constructing k basis vectors and applied to a square system BAx=Bb.
+#' Dense matrices constructed and stored by this method are of size (length(x), k) and (k+1, k) where k is GMRES current basis vector number. If maxit > k, GMRES will be restarted after each k iterations
+#' Particularity of this implementation that matrices A and B have no to be stored explicitly.
+#' User provides just callback function mimicking their multiplication by adequate vectors.
+#' In case of non convergence after maxit iterations, attr(x) will contain a field 'warning' with the message which will be also issued with warning()
+#' If the operator BA is not of full rank, iterations will be stopped before reaching convergence or maxit. A warning will be emitted in this case.
+#'
+#' @examples
+#' # prepare a 4x3 toy problem Ax=b
+#' A=rbind(diag(1:3)+matrix(1, 3,3), rep(1, 3))
+#' xsol=1:3
+#' b=A%*%xsol
+#' f_Ax=function(x,...) list(...)$A%*%x
+#' f_Br=function(r,...) t(list(...)$A)%*%r
+#' x=gmresls(f_Ax, f_Br, b, A=A)
+#' @export
+gmresls=function(f_Ax, f_Br, b, x0=NULL, k=0, maxit=0, tol=1.e-7, ...) {
+  f_ba=function(x, ...) f_Br(f_Ax(x, ...), ...)
+  bb=f_Br(b, ...)
+  nbb=sqrt(sum(bb*bb)) # useful for convergence detection
+  nx=length(bb)
+  if (maxit == 0L)
+    maxit=nx
+  it=0L
+  warn=""
+  repeat { # restart till convergence or maxit reached
+    it=it+1L
+    r0=bb
+    if (!is.null(x0))
+      r0=r0-f_ba(x0, ...)
+    nr0=sqrt(sum(r0*r0))
+    v=as.matrix(r0/nr0)
+    h=matrix(0., 1L, 0L) # Heisenberg matrix, will be growing
+    re1=nr0
+    if (k == 0)
+      k=nx
+    converged=nr0/nbb <= tol
+    for (i in seq_len(k)) {
+      if (converged)
+        return(if (is.null(x0)) double(nx) else x0)
+      bavi=f_ba(v[,i], ...)
+      h=cbind(h, crossprod(v, bavi))
+      v=cbind(v, bavi-v%*%h[,i])
+      h=rbind(h, 0.)
+      h[i+1L,i]=sqrt(crossprod(v[,i+1]))
+      if (h[i+1L,i] <= tol) {
+        re1=c(re1, 0.)
+        y=qr.solve(h, re1)
+        nri=sqrt(crossprod(h%*%y-re1))
+        converged=nri/nr0 <= tol
+        if (it < nx)
+          warn="BA is not of full rank\n"
+        break
+      } else {
+        v[,i+1L]=v[,i+1L]/h[i+1L,i]
+      }
+      re1=c(re1, 0.)
+      y=qr.solve(h, re1)
+      nri=sqrt(crossprod(h%*%y-re1))
+      converged=nri/nr0 <= tol
+      it=it+1L
+    }
+    x=v[,-ncol(v)]%*%y
+    if( !is.null(x0) )
+      x=x+x0
+    # go to restart
+    if (converged || it >= maxit)
+      break
+  }
+  if (!converged)
+    warn=paste0(warn, "Maximal iteration number (", maxit, ") is reached without convergence")
+  if (nchar(warn))
+    warning(attr(x, "warning") <- warn)
+
+  attr(x, "resid")=nri
+  attr(x, "iteration")=it
+  x
+}
diff --git a/inst/unitTests/runit.gmresls.R b/inst/unitTests/runit.gmresls.R
new file mode 100644
index 0000000000000000000000000000000000000000..0289ff1ac0583127d01adc559f4cf7a68c44a031
--- /dev/null
+++ b/inst/unitTests/runit.gmresls.R
@@ -0,0 +1,11 @@
+# doc example
+# prepare a 4x3 toy problem Ax=b
+A=rbind(diag(1:3)+matrix(1, 3,3), rep(1, 3))
+xsol=1:3
+b=A%*%xsol
+f_Ax=function(x,...) list(...)$A%*%x
+f_Br=function(r,...) t(list(...)$A)%*%r
+x=gmresls(f_Ax, f_Br, b, A=A)
+test.doc <- function() {
+  checkEqualsNumeric(x, xsol)
+}
diff --git a/man/gmresls.Rd b/man/gmresls.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..36301c6ccc29a9cdd1527fcc9ae896a5f1014bd9
--- /dev/null
+++ b/man/gmresls.Rd
@@ -0,0 +1,49 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/gmresls.R
+\name{gmresls}
+\alias{gmresls}
+\title{Solve a Least Squares System with a Preconditioner.}
+\usage{
+gmresls(f_Ax, f_Br, b, x0 = NULL, k = 0, maxit = 0, tol = 1e-07, ...)
+}
+\arguments{
+\item{f_Ax}{A function calculating matrix-vector product Ax for a given x}
+
+\item{f_Br}{A function calculating matrix-vector product Br for a given r}
+
+\item{b}{A vector of right hand side}
+
+\item{x0}{A vector or NULL (which means 0), initial approximation for Ax=b}
+
+\item{k}{An integer, parameter for restarting GMRES. Value 0 (default) means no restart, i.e. at most length(x) basis vectors will be constructed and used.}
+
+\item{maxit}{A maximal iteration number. Default (0) means length(x).}
+
+\item{tol}{A tolerance for solution, estimated as ||B(Ax-b)||/||Bb||, default 1.e-7}
+
+\item{...}{Parameters passed through to f_ax and f_br}
+}
+\value{
+The solution x, having the structure of Bb.
+}
+\description{
+Solve a least squares system Ax=b (dim(A)=(m,n) with m >= n) with a
+preconditioner B: BAx=Bb (dim(B)=(n,m)).
+Implemented method uses GMRES(k) with callback functions, i.e. no explicit A or B are required. GMRES can be restarted after k iterations.
+}
+\details{
+Implemented method is equivalent to a classical GMRES(k) method with restart after constructing k basis vectors and applied to a system BAx=Bb.
+Dense matrices constructed and stored by this method are of size (length(x), k) and (k+1, k) where k is GMRES current basis vector number. If maxit > k, GMRES will be restarted after each k iterations
+Particularity of this implementation that matrices A and B have no to be stored explicitly.
+User provides just callback function mimicking their multiplication by adequate vectors.
+In case of non convergence after maxit iterations, attr(x) will contain a field 'warning' with the message which will be also issued with warning()
+}
+\examples{
+# prepare a 4x3 toy problem Ax=b
+A=rbind(diag(1:3)+matrix(1, 3,3), rep(1, 3))
+xsol=1:3
+b=A\%*\%xsol
+f_Ax=function(x,...) list(...)$A\%*\%x
+f_Br=function(r,...) t(list(...)$A)\%*\%r
+x=gmresls(f_Ax, f_Br, b, A=A)
+}
diff --git a/tests/RUnit.R b/tests/RUnit.R
new file mode 100644
index 0000000000000000000000000000000000000000..f4ddaa69752e2b1a304b69036c14628011ffe836
--- /dev/null
+++ b/tests/RUnit.R
@@ -0,0 +1,18 @@
+if (requireNamespace("RUnit", quietly=TRUE) && requireNamespace("gmresls", quietly=TRUE)) {
+   library(RUnit)
+   library(gmresls)
+
+   testSuite <- defineTestSuite(
+      name = "gmresls unit tests",
+      dirs = system.file("unitTests", package = "gmresls"),
+      testFuncRegexp = "^[Tt]est.+",
+      rngKind = RNGkind()[1L]
+   )
+   Sys.setenv("R_TESTS"="")
+   tests <- runTestSuite(testSuite)
+
+   printTextProtocol(tests)
+
+   if (getErrors(tests)$nFail > 0) stop("RUnit test failure")
+   if (getErrors(tests)$nErr > 0) stop("Errors in RUnit tests")
+}