[{"id":34099,"web_url":"https://patchwork.libcamera.org/comment/34099/","msgid":"<174617218287.1586992.12766708855039261289@ping.linuxembedded.co.uk>","date":"2025-05-02T07:49:42","subject":"Re: [PATCH v3 05/16] libcamera: matrix: Add inverse() function","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Stefan Klug (2025-04-03 16:49:10)\n> For calculations in upcoming algorithm patches, the inverse of a matrix\n> is required. Add an implementation of the inverse() function for square\n> matrices.\n> \n> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> \n\nGiven there's a test structure added after this...\n\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\nMaybe that should be just acked by as I'm not spending a lot of time to\nreview the Gaussian Ellimination here - but I trust you and\nLaurent and the results of some tests being added to the unit tests for\nit so I'm fine with it ;-)\n\n> ---\n> \n> Changes in v2:\n> - Replaced the implementation by a generic one provided by Laurent that\n>   supports arbitrary square matrices instead of 2x2 and 3x3 only.\n> - Moved the implementation into the cpp file.\n> \n> Changes inv3:\n> - Added stack allocated scratchBuffers to matrixInvert so that no heap\n>   allocations are necessary.\n> ---\n>  include/libcamera/internal/matrix.h |  23 ++++\n>  src/libcamera/matrix.cpp            | 166 ++++++++++++++++++++++++++++\n>  2 files changed, 189 insertions(+)\n> \n> diff --git a/include/libcamera/internal/matrix.h b/include/libcamera/internal/matrix.h\n> index 6d40567af0a0..a07a47701336 100644\n> --- a/include/libcamera/internal/matrix.h\n> +++ b/include/libcamera/internal/matrix.h\n> @@ -19,6 +19,12 @@ namespace libcamera {\n>  \n>  LOG_DECLARE_CATEGORY(Matrix)\n>  \n> +#ifndef __DOXYGEN__\n> +template<typename T>\n> +bool matrixInvert(Span<const T> dataIn, Span<T> dataOut, unsigned int dim,\n> +                 Span<T> scratchBuffer, Span<unsigned int> swapBuffer);\n> +#endif /* __DOXYGEN__ */\n> +\n>  template<typename T, unsigned int Rows, unsigned int Cols>\n>  class Matrix\n>  {\n> @@ -91,6 +97,23 @@ public:\n>                 return *this;\n>         }\n>  \n> +       Matrix<T, Rows, Cols> inverse(bool *ok = nullptr) const\n> +       {\n> +               static_assert(Rows == Cols, \"Matrix must be square\");\n> +\n> +               Matrix<T, Rows, Cols> inverse;\n> +               std::array<T, Rows * Cols * 2> scratchBuffer;\n> +               std::array<unsigned int, Rows> swapBuffer;\n> +               bool res = matrixInvert(Span<const T>(data_),\n> +                                       Span<T>(inverse.data_),\n> +                                       Rows,\n> +                                       Span<T>(scratchBuffer),\n> +                                       Span<unsigned int>(swapBuffer));\n> +               if (ok)\n> +                       *ok = res;\n> +               return inverse;\n> +       }\n> +\n>  private:\n>         /*\n>          * \\todo The initializer is only necessary for the constructor to be\n> diff --git a/src/libcamera/matrix.cpp b/src/libcamera/matrix.cpp\n> index 49e2aa3b4f2c..68fc1b7bd5ac 100644\n> --- a/src/libcamera/matrix.cpp\n> +++ b/src/libcamera/matrix.cpp\n> @@ -7,6 +7,12 @@\n>  \n>  #include \"libcamera/internal/matrix.h\"\n>  \n> +#include <algorithm>\n> +#include <assert.h>\n> +#include <cmath>\n> +#include <numeric>\n> +#include <vector>\n> +\n>  #include <libcamera/base/log.h>\n>  \n>  /**\n> @@ -87,6 +93,20 @@ LOG_DEFINE_CATEGORY(Matrix)\n>   * \\return Row \\a i from the matrix, as a Span\n>   */\n>  \n> +/**\n> + * \\fn Matrix::inverse(bool *ok) const\n> + * \\param[out] ok Indicate if the matrix was successfully inverted\n> + * \\brief Compute the inverse of the matrix\n> + *\n> + * This function computes the inverse of the matrix. It is only implemented for\n> + * matrices of float and double types. If \\a ok is provided it will be set to a\n> + * boolean value to indicate of the inversion was successful. This can be used\n> + * to check if the matrix is singular, in which case the function will return\n> + * an identity matrix.\n> + *\n> + * \\return The inverse of the matrix\n> + */\n> +\n>  /**\n>   * \\fn Matrix::operator[](size_t i)\n>   * \\copydoc Matrix::operator[](size_t i) const\n> @@ -142,6 +162,152 @@ LOG_DEFINE_CATEGORY(Matrix)\n>   */\n>  \n>  #ifndef __DOXYGEN__\n> +template<typename T>\n> +bool matrixInvert(Span<const T> dataIn, Span<T> dataOut, unsigned int dim,\n> +                 Span<T> scratchBuffer, Span<unsigned int> swapBuffer)\n> +{\n> +       /*\n> +        * Convenience class to access matrix data, providing a row-major (i,j)\n> +        * element accessor through the call operator, and the ability to swap\n> +        * rows without modifying the backing storage.\n> +        */\n> +       class MatrixAccessor\n> +       {\n> +       public:\n> +               MatrixAccessor(Span<T> data, Span<unsigned int> swapBuffer, unsigned int rows, unsigned int cols)\n> +                       : data_(data), swap_(swapBuffer), rows_(rows), cols_(cols)\n> +               {\n> +                       ASSERT(swap_.size() == rows);\n> +                       std::iota(swap_.begin(), swap_.end(), T{ 0 });\n> +               }\n> +\n> +               T &operator()(unsigned int row, unsigned int col)\n> +               {\n> +                       assert(row < rows_ && col < cols_);\n> +                       return data_[index(row, col)];\n> +               }\n> +\n> +               void swap(unsigned int a, unsigned int b)\n> +               {\n> +                       assert(a < rows_ && a < cols_);\n> +                       std::swap(swap_[a], swap_[b]);\n> +               }\n> +\n> +       private:\n> +               unsigned int index(unsigned int row, unsigned int col) const\n> +               {\n> +                       return swap_[row] * cols_ + col;\n> +               }\n> +\n> +               Span<T> data_;\n> +               Span<unsigned int> swap_;\n> +               unsigned int rows_;\n> +               unsigned int cols_;\n> +       };\n> +\n> +       /*\n> +        * Matrix inversion using Gaussian elimination.\n> +        *\n> +        * Start by augmenting the original matrix with an identiy matrix of\n> +        * the same size.\n> +        */\n> +       ASSERT(scratchBuffer.size() == dim * dim * 2);\n> +       MatrixAccessor matrix(scratchBuffer, swapBuffer, dim, dim * 2);\n> +\n> +       for (unsigned int i = 0; i < dim; ++i) {\n> +               for (unsigned int j = 0; j < dim; ++j) {\n> +                       matrix(i, j) = dataIn[i * dim + j];\n> +                       matrix(i, j + dim) = T{ 0 };\n> +               }\n> +               matrix(i, i + dim) = T{ 1 };\n> +       }\n> +\n> +       /* Start by triangularizing the input . */\n> +       for (unsigned int pivot = 0; pivot < dim; ++pivot) {\n> +               /*\n> +                * Locate the next pivot. To improve numerical stability, use\n> +                * the row with the largest value in the pivot's column.\n> +                */\n> +               unsigned int row;\n> +               T maxValue{ 0 };\n> +\n> +               for (unsigned int i = pivot; i < dim; ++i) {\n> +                       T value = std::abs(matrix(i, pivot));\n> +                       if (maxValue < value) {\n> +                               maxValue = value;\n> +                               row = i;\n> +                       }\n> +               }\n> +\n> +               /*\n> +                * If no pivot is found in the column, the matrix is not\n> +                * invertible. Return an identity matrix.\n> +                */\n> +               if (maxValue == 0) {\n> +                       std::fill(dataOut.begin(), dataOut.end(), T{ 0 });\n> +                       for (unsigned int i = 0; i < dim; ++i)\n> +                               dataOut[i * dim + i] = T{ 1 };\n> +                       return false;\n> +               }\n> +\n> +               /* Swap rows to bring the pivot in the right location. */\n> +               matrix.swap(pivot, row);\n> +\n> +               /* Process all rows below the pivot to zero the pivot column. */\n> +               const T pivotValue = matrix(pivot, pivot);\n> +\n> +               for (unsigned int i = pivot + 1; i < dim; ++i) {\n> +                       const T factor = matrix(i, pivot) / pivotValue;\n> +\n> +                       /*\n> +                        * We know the element in the pivot column will be 0,\n> +                        * hardcode it instead of computing it.\n> +                        */\n> +                       matrix(i, pivot) = T{ 0 };\n> +\n> +                       for (unsigned int j = pivot + 1; j < dim * 2; ++j)\n> +                               matrix(i, j) -= matrix(pivot, j) * factor;\n> +               }\n> +       }\n> +\n> +       /*\n> +        * Then diagonalize the input, walking the diagonal backwards. There's\n> +        * no need to update the input matrix, as all the values we would write\n> +        * in the top-right triangle aren't used in further calculations (and\n> +        * would all by definition be zero).\n> +        */\n> +       for (unsigned int pivot = dim - 1; pivot > 0; --pivot) {\n> +               const T pivotValue = matrix(pivot, pivot);\n> +\n> +               for (unsigned int i = 0; i < pivot; ++i) {\n> +                       const T factor = matrix(i, pivot) / pivotValue;\n> +\n> +                       for (unsigned int j = dim; j < dim * 2; ++j)\n> +                               matrix(i, j) -= matrix(pivot, j) * factor;\n> +               }\n> +       }\n> +\n> +       /*\n> +        * Finally, normalize the diagonal and store the result in the output\n> +        * data.\n> +        */\n> +       for (unsigned int i = 0; i < dim; ++i) {\n> +               const T factor = matrix(i, i);\n> +\n> +               for (unsigned int j = 0; j < dim; ++j)\n> +                       dataOut[i * dim + j] = matrix(i, j + dim) / factor;\n> +       }\n> +\n> +       return true;\n> +}\n> +\n> +template bool matrixInvert<float>(Span<const float> dataIn, Span<float> dataOut,\n> +                                 unsigned int dim, Span<float> scratchBuffer,\n> +                                 Span<unsigned int> swapBuffer);\n> +template bool matrixInvert<double>(Span<const double> data, Span<double> dataOut,\n> +                                  unsigned int dim, Span<double> scratchBuffer,\n> +                                  Span<unsigned int> swapBuffer);\n> +\n>  /*\n>   * The YAML data shall be a list of numerical values. Its size shall be equal\n>   * to the product of the number of rows and columns of the matrix (Rows x\n> -- \n> 2.43.0\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 52416C327D\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  2 May 2025 07:49:48 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 9F99168AD8;\n\tFri,  2 May 2025 09:49:47 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 39DBA68ACB\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  2 May 2025 09:49:46 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 57BF5353;\n\tFri,  2 May 2025 09:49:38 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"klb1vlqb\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1746172178;\n\tbh=AekfZ2XSj2hZz5/27FMz54wc+/YEZ9YlY8YKCeE9VSY=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=klb1vlqbhVkvDjT8Tpk7zXivAg9JZ8LvROWpwhCqEFrwllqoPAYCQEvJdUaM4Dgr2\n\ttLVdPkQNSZVqOlMGON39kIA8uTYRrfA0Pox5pCkOOr4fRXHyXxG8HzcQqOKip84MPy\n\tqKku3ArxZfSM60eaE5vthA3LMnlzvXZKI+0Q2PV8=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250403154925.382973-6-stefan.klug@ideasonboard.com>","References":"<20250403154925.382973-1-stefan.klug@ideasonboard.com>\n\t<20250403154925.382973-6-stefan.klug@ideasonboard.com>","Subject":"Re: [PATCH v3 05/16] libcamera: matrix: Add inverse() function","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Stefan Klug <stefan.klug@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Stefan Klug <stefan.klug@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Fri, 02 May 2025 08:49:42 +0100","Message-ID":"<174617218287.1586992.12766708855039261289@ping.linuxembedded.co.uk>","User-Agent":"alot/0.10","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":34142,"web_url":"https://patchwork.libcamera.org/comment/34142/","msgid":"<aBuNUcxripxqZiGP@pyrite.rasen.tech>","date":"2025-05-07T16:41:53","subject":"Re: [PATCH v3 05/16] libcamera: matrix: Add inverse() function","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"On Fri, May 02, 2025 at 08:49:42AM +0100, Kieran Bingham wrote:\n> Quoting Stefan Klug (2025-04-03 16:49:10)\n> > For calculations in upcoming algorithm patches, the inverse of a matrix\n> > is required. Add an implementation of the inverse() function for square\n> > matrices.\n> > \n> > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > \n> \n> Given there's a test structure added after this...\n> \n> \n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> \n> Maybe that should be just acked by as I'm not spending a lot of time to\n> review the Gaussian Ellimination here - but I trust you and\n> Laurent and the results of some tests being added to the unit tests for\n> it so I'm fine with it ;-)\n\nI went through and reviewed the Gaussian Elimination...\n\nReviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\n> \n> > ---\n> > \n> > Changes in v2:\n> > - Replaced the implementation by a generic one provided by Laurent that\n> >   supports arbitrary square matrices instead of 2x2 and 3x3 only.\n> > - Moved the implementation into the cpp file.\n> > \n> > Changes inv3:\n> > - Added stack allocated scratchBuffers to matrixInvert so that no heap\n> >   allocations are necessary.\n> > ---\n> >  include/libcamera/internal/matrix.h |  23 ++++\n> >  src/libcamera/matrix.cpp            | 166 ++++++++++++++++++++++++++++\n> >  2 files changed, 189 insertions(+)\n> > \n> > diff --git a/include/libcamera/internal/matrix.h b/include/libcamera/internal/matrix.h\n> > index 6d40567af0a0..a07a47701336 100644\n> > --- a/include/libcamera/internal/matrix.h\n> > +++ b/include/libcamera/internal/matrix.h\n> > @@ -19,6 +19,12 @@ namespace libcamera {\n> >  \n> >  LOG_DECLARE_CATEGORY(Matrix)\n> >  \n> > +#ifndef __DOXYGEN__\n> > +template<typename T>\n> > +bool matrixInvert(Span<const T> dataIn, Span<T> dataOut, unsigned int dim,\n> > +                 Span<T> scratchBuffer, Span<unsigned int> swapBuffer);\n> > +#endif /* __DOXYGEN__ */\n> > +\n> >  template<typename T, unsigned int Rows, unsigned int Cols>\n> >  class Matrix\n> >  {\n> > @@ -91,6 +97,23 @@ public:\n> >                 return *this;\n> >         }\n> >  \n> > +       Matrix<T, Rows, Cols> inverse(bool *ok = nullptr) const\n> > +       {\n> > +               static_assert(Rows == Cols, \"Matrix must be square\");\n> > +\n> > +               Matrix<T, Rows, Cols> inverse;\n> > +               std::array<T, Rows * Cols * 2> scratchBuffer;\n> > +               std::array<unsigned int, Rows> swapBuffer;\n> > +               bool res = matrixInvert(Span<const T>(data_),\n> > +                                       Span<T>(inverse.data_),\n> > +                                       Rows,\n> > +                                       Span<T>(scratchBuffer),\n> > +                                       Span<unsigned int>(swapBuffer));\n> > +               if (ok)\n> > +                       *ok = res;\n> > +               return inverse;\n> > +       }\n> > +\n> >  private:\n> >         /*\n> >          * \\todo The initializer is only necessary for the constructor to be\n> > diff --git a/src/libcamera/matrix.cpp b/src/libcamera/matrix.cpp\n> > index 49e2aa3b4f2c..68fc1b7bd5ac 100644\n> > --- a/src/libcamera/matrix.cpp\n> > +++ b/src/libcamera/matrix.cpp\n> > @@ -7,6 +7,12 @@\n> >  \n> >  #include \"libcamera/internal/matrix.h\"\n> >  \n> > +#include <algorithm>\n> > +#include <assert.h>\n> > +#include <cmath>\n> > +#include <numeric>\n> > +#include <vector>\n> > +\n> >  #include <libcamera/base/log.h>\n> >  \n> >  /**\n> > @@ -87,6 +93,20 @@ LOG_DEFINE_CATEGORY(Matrix)\n> >   * \\return Row \\a i from the matrix, as a Span\n> >   */\n> >  \n> > +/**\n> > + * \\fn Matrix::inverse(bool *ok) const\n> > + * \\param[out] ok Indicate if the matrix was successfully inverted\n> > + * \\brief Compute the inverse of the matrix\n> > + *\n> > + * This function computes the inverse of the matrix. It is only implemented for\n> > + * matrices of float and double types. If \\a ok is provided it will be set to a\n> > + * boolean value to indicate of the inversion was successful. This can be used\n> > + * to check if the matrix is singular, in which case the function will return\n> > + * an identity matrix.\n> > + *\n> > + * \\return The inverse of the matrix\n> > + */\n> > +\n> >  /**\n> >   * \\fn Matrix::operator[](size_t i)\n> >   * \\copydoc Matrix::operator[](size_t i) const\n> > @@ -142,6 +162,152 @@ LOG_DEFINE_CATEGORY(Matrix)\n> >   */\n> >  \n> >  #ifndef __DOXYGEN__\n> > +template<typename T>\n> > +bool matrixInvert(Span<const T> dataIn, Span<T> dataOut, unsigned int dim,\n> > +                 Span<T> scratchBuffer, Span<unsigned int> swapBuffer)\n> > +{\n> > +       /*\n> > +        * Convenience class to access matrix data, providing a row-major (i,j)\n> > +        * element accessor through the call operator, and the ability to swap\n> > +        * rows without modifying the backing storage.\n> > +        */\n> > +       class MatrixAccessor\n> > +       {\n> > +       public:\n> > +               MatrixAccessor(Span<T> data, Span<unsigned int> swapBuffer, unsigned int rows, unsigned int cols)\n> > +                       : data_(data), swap_(swapBuffer), rows_(rows), cols_(cols)\n> > +               {\n> > +                       ASSERT(swap_.size() == rows);\n> > +                       std::iota(swap_.begin(), swap_.end(), T{ 0 });\n> > +               }\n> > +\n> > +               T &operator()(unsigned int row, unsigned int col)\n> > +               {\n> > +                       assert(row < rows_ && col < cols_);\n> > +                       return data_[index(row, col)];\n> > +               }\n> > +\n> > +               void swap(unsigned int a, unsigned int b)\n> > +               {\n> > +                       assert(a < rows_ && a < cols_);\n> > +                       std::swap(swap_[a], swap_[b]);\n> > +               }\n> > +\n> > +       private:\n> > +               unsigned int index(unsigned int row, unsigned int col) const\n> > +               {\n> > +                       return swap_[row] * cols_ + col;\n> > +               }\n> > +\n> > +               Span<T> data_;\n> > +               Span<unsigned int> swap_;\n> > +               unsigned int rows_;\n> > +               unsigned int cols_;\n> > +       };\n> > +\n> > +       /*\n> > +        * Matrix inversion using Gaussian elimination.\n> > +        *\n> > +        * Start by augmenting the original matrix with an identiy matrix of\n> > +        * the same size.\n> > +        */\n> > +       ASSERT(scratchBuffer.size() == dim * dim * 2);\n> > +       MatrixAccessor matrix(scratchBuffer, swapBuffer, dim, dim * 2);\n> > +\n> > +       for (unsigned int i = 0; i < dim; ++i) {\n> > +               for (unsigned int j = 0; j < dim; ++j) {\n> > +                       matrix(i, j) = dataIn[i * dim + j];\n> > +                       matrix(i, j + dim) = T{ 0 };\n> > +               }\n> > +               matrix(i, i + dim) = T{ 1 };\n> > +       }\n> > +\n> > +       /* Start by triangularizing the input . */\n> > +       for (unsigned int pivot = 0; pivot < dim; ++pivot) {\n> > +               /*\n> > +                * Locate the next pivot. To improve numerical stability, use\n> > +                * the row with the largest value in the pivot's column.\n> > +                */\n> > +               unsigned int row;\n> > +               T maxValue{ 0 };\n> > +\n> > +               for (unsigned int i = pivot; i < dim; ++i) {\n> > +                       T value = std::abs(matrix(i, pivot));\n> > +                       if (maxValue < value) {\n> > +                               maxValue = value;\n> > +                               row = i;\n> > +                       }\n> > +               }\n> > +\n> > +               /*\n> > +                * If no pivot is found in the column, the matrix is not\n> > +                * invertible. Return an identity matrix.\n> > +                */\n> > +               if (maxValue == 0) {\n> > +                       std::fill(dataOut.begin(), dataOut.end(), T{ 0 });\n> > +                       for (unsigned int i = 0; i < dim; ++i)\n> > +                               dataOut[i * dim + i] = T{ 1 };\n> > +                       return false;\n> > +               }\n> > +\n> > +               /* Swap rows to bring the pivot in the right location. */\n> > +               matrix.swap(pivot, row);\n> > +\n> > +               /* Process all rows below the pivot to zero the pivot column. */\n> > +               const T pivotValue = matrix(pivot, pivot);\n> > +\n> > +               for (unsigned int i = pivot + 1; i < dim; ++i) {\n> > +                       const T factor = matrix(i, pivot) / pivotValue;\n> > +\n> > +                       /*\n> > +                        * We know the element in the pivot column will be 0,\n> > +                        * hardcode it instead of computing it.\n> > +                        */\n> > +                       matrix(i, pivot) = T{ 0 };\n> > +\n> > +                       for (unsigned int j = pivot + 1; j < dim * 2; ++j)\n> > +                               matrix(i, j) -= matrix(pivot, j) * factor;\n> > +               }\n> > +       }\n> > +\n> > +       /*\n> > +        * Then diagonalize the input, walking the diagonal backwards. There's\n> > +        * no need to update the input matrix, as all the values we would write\n> > +        * in the top-right triangle aren't used in further calculations (and\n> > +        * would all by definition be zero).\n> > +        */\n> > +       for (unsigned int pivot = dim - 1; pivot > 0; --pivot) {\n> > +               const T pivotValue = matrix(pivot, pivot);\n> > +\n> > +               for (unsigned int i = 0; i < pivot; ++i) {\n> > +                       const T factor = matrix(i, pivot) / pivotValue;\n> > +\n> > +                       for (unsigned int j = dim; j < dim * 2; ++j)\n> > +                               matrix(i, j) -= matrix(pivot, j) * factor;\n> > +               }\n> > +       }\n> > +\n> > +       /*\n> > +        * Finally, normalize the diagonal and store the result in the output\n> > +        * data.\n> > +        */\n> > +       for (unsigned int i = 0; i < dim; ++i) {\n> > +               const T factor = matrix(i, i);\n> > +\n> > +               for (unsigned int j = 0; j < dim; ++j)\n> > +                       dataOut[i * dim + j] = matrix(i, j + dim) / factor;\n> > +       }\n> > +\n> > +       return true;\n> > +}\n> > +\n> > +template bool matrixInvert<float>(Span<const float> dataIn, Span<float> dataOut,\n> > +                                 unsigned int dim, Span<float> scratchBuffer,\n> > +                                 Span<unsigned int> swapBuffer);\n> > +template bool matrixInvert<double>(Span<const double> data, Span<double> dataOut,\n> > +                                  unsigned int dim, Span<double> scratchBuffer,\n> > +                                  Span<unsigned int> swapBuffer);\n> > +\n> >  /*\n> >   * The YAML data shall be a list of numerical values. Its size shall be equal\n> >   * to the product of the number of rows and columns of the matrix (Rows x\n> > -- \n> > 2.43.0\n> >","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 94BABC3226\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  7 May 2025 16:41:59 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id BFED168B31;\n\tWed,  7 May 2025 18:41:58 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8BE5168AD8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  7 May 2025 18:41:57 +0200 (CEST)","from pyrite.rasen.tech (unknown\n\t[IPv6:2001:861:3a80:3300:4f2f:8c2c:b3ef:17d4])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C49806D5;\n\tWed,  7 May 2025 18:41:45 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"V4uD75o9\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1746636105;\n\tbh=Bh8kZi7nmJX/j0a6Eoi5rsKs5xKs7xUbjWlniFNpK2Y=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=V4uD75o9f+J4FfjzkbSEQJ5DWHG2bcVMiYLJw5RQj8wtO3IjiPG2M66SIHKwgCeks\n\tOd/mM7GZe2s1BaFDT8BagqX1z5Vtpn2nU+TD4bqn5vH3Kpqa6koU+1lTLdrXyay9Cp\n\t7DCNdWXLT5XaG9xGy5x5Gm1b/qKev5+QLg1eoIBo=","Date":"Wed, 7 May 2025 18:41:53 +0200","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Stefan Klug <stefan.klug@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>","Subject":"Re: [PATCH v3 05/16] libcamera: matrix: Add inverse() function","Message-ID":"<aBuNUcxripxqZiGP@pyrite.rasen.tech>","References":"<20250403154925.382973-1-stefan.klug@ideasonboard.com>\n\t<20250403154925.382973-6-stefan.klug@ideasonboard.com>\n\t<174617218287.1586992.12766708855039261289@ping.linuxembedded.co.uk>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<174617218287.1586992.12766708855039261289@ping.linuxembedded.co.uk>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]