首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >openblas系列之qr分解

openblas系列之qr分解

作者头像
用户10637292
发布2026-07-13 21:10:44
发布2026-07-13 21:10:44
90
举报

本节我们探讨openblas系列中的QR分解。

关于安装以及行优先、列优先的前置知识,请参考本系列的第一篇文章。

1.函数说明

1.1 函数公式

代码语言:javascript
复制
A = Q * R

将输入矩阵A分解为Q矩阵和上三角矩阵R的乘积。这里需要区分完全QR分解和经济QR分解。完全QR分解,Q为m*m的矩阵,R为m*n的矩阵。经济QR分解,Q为m*n的矩阵,R为n*n的矩阵。本节将会使用完全QR分解来演示。

1.2 函数定义

Q和R是拆分为两个函数分别求取的。

fortran形式

代码语言:javascript
复制
// 为了方便展示,将名类型替换为真实类型
// 第一个函数sgeqrf,用于求解R矩阵,以及生成Q矩阵的中间因子
// https://netlib.org/lapack/explore-html//d0/da1/group__geqrf_ga01418ec8a343078109cde4febb6cda7d.html
int sgeqrf_(
        int* m,//行数
        int* n,//列数
        float* a,//输入: 待分解矩阵A的m*n矩阵. 输出:上半部分存R,下半部分存用于构造Q的householder向量
        int* lda,//矩阵A的主维度
        float* tau,//输出 长度为min(m,n)的数组,存储householder变换的标量因子
        float* work,//工作空间,临时缓冲区,存储中间计算结果
        int* lwork,//work数组的大小,若为-1,则函数会进行工作空间查询,返回最优大小
        int* info);//输出,状态标志
// 第二个函数sorgqr,用于生成矩阵Q
// https://netlib.org/lapack/explore-html/d4/dfc/group__ungqr_gacb2375e4540a08ea3c59dcfea6701ca2.html
int sorgqr_(
        int* m,// Q的行数
        int* n,// 想要生成Q的列数.通常N=M或N=K(生成经济型矩阵)
        int* k,// 变换的阶数,通常等于sgeqrf_中的min(M,N)
        float* a,// 输入: sgeqrf_处理后的矩阵A(包含householder向量)  输出:生成的显式Q矩阵(会覆盖输入内容)
        int* lda,// 矩阵A的主维数
        float* tau,// sgeqrf_生成的标量因子数组
        float* work,// 工作空间,临时缓冲区
        int* lwork,// work数组大小
        int* info);// 状态码. 0成功

lapack形式

代码语言:javascript
复制
// 为了方便展示,将名类型替换为真实类型
// 函数路径:/opt/homebrew/opt/openblas/include/lapacke.h
// 第一个函数,用于求解R矩阵,以及生成Q矩阵的中间因子
int LAPACKE_sgeqrf( int matrix_layout, // 内存布局,行优先:LAPACK_ROW_MAJOR  列优先:LAPACK_COL_MAJOR
    int m, // 输入矩阵A的行数
    int n,// 输入矩阵A的列数
    float* a, // 输入矩阵A,同时也是输出矩阵。上三角部分为R矩阵,下半部分存用于构造Q的householder向量
    int lda, // 输入矩阵A的leading dimension.如果为行优先,即为列数。如果为列优先,即为行数
    float* tau ); // 输出值。长度为min(m,n)的数组,存储householder变换的标量因子
 // 第二各函数,用于生成Q矩阵
 int LAPACKE_sorgqr( int matrix_layout, // 内存布局,行优先:LAPACK_ROW_MAJOR  列优先:LAPACK_COL_MAJOR  
     int m, // Q的行数
     int n, // Q的列数
     int k, // 变换的阶数,通常为min(m,n)
     float* a, // 输入: LAPACKE_sgeqrf处理后的矩阵A(包含householder向量)  输出:生成的显式Q矩阵(会覆盖输入内容)
     int lda, // 矩阵Q的leading dimension。行优先即为列数,列优先即为行数
     const float* tau);// 输入值,LAPACKE_sgeqrf生成的中间因子

在内存布局方面,fortran原生接口默认采用列优先存储,而lapack接口可通过首个参数matrix_layout控制,支持开发者在行优先和列优先之间选择。本节内容以lapack形式为主。

2. demo示例

代码语言:javascript
复制
#include <algorithm>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>
// 注意:本节所用的QR分解只需包含lapacke.h头文件即可。cblas.h文件是为了重建验证使用的
#include <cblas.h>
#include <lapacke.h>
using std::cout;
using std::endl;
using std::string;
using std::vector;
/**
    按照行展示的方式打印列优先存储的矩阵
    name: 名字。自定义字符串
    matrix: 矩阵
    rows: 行数
    cols: 列数
*/
void print_col_matrix_by_row(string name, float* matrix, int rows, int cols) {
  cout << name << ":" << rows << "x" << cols << endl;
  for (size_t i = 0; i < rows; i++) {
    for (size_t j = 0; j < cols; j++) {
      cout << matrix[j * rows + i] << "\t";
    }
    cout << endl;
  }
}
/**
    QR分解公式:A = Q*R
*/
int main(int argc, char* argv[]) {
  int M = 3;
  int N = 2;
  /**列优先,按照行展示
  A:
   12 -51
   6 167
   -4 24
  */
  // 1.构造输入数据
  vector<float> A = {12, 6, -4, -51, 167, 24};  // 内存布局使用列优先
  vector<float> tau(std::min(M, N), 0);  // 中间变换因子,长度为min(M,N)的数组
  // 2. 执行第一步分解,拿到上三角矩阵R
  LAPACKE_sgeqrf(
      LAPACK_COL_MAJOR,  // 列优先存储
      M, N,              // 输入矩阵A的行和列
      A.data(),  // 输入矩阵A的原始数据,输出的R矩阵会存放在A的上三角矩阵
      M,  // 输入矩阵A的leading-dimension, 因为为列优先,即为行数3
      tau.data());  // 中间变换因子,用于继续求解Q矩阵
  // 3.
  // 打印R矩阵。A矩阵分为两部分,上三角矩阵为R,下三角矩阵为生成Q矩阵所需的数据。
  print_col_matrix_by_row("R", A.data(), M, N);
  // 4. 以下为生成Q矩阵的过程
  vector<float> Q(M * M);  // 生成完全Q矩阵即为m*m的大小。
  std::memcpy(Q.data(), A.data(),
              M * N * sizeof(float));  // sgeqrf处理后的矩阵A
  LAPACKE_sorgqr(LAPACK_COL_MAJOR,     // 内存布局使用列优先
                 M, M,                 // 矩阵Q的行数和列数
                 M,  // 矩阵Q的leading-dimension,因为是列优先,所以是行数3
                 Q.data(),  // 输入矩阵,同时也是输出矩阵
                 M,  // 输入矩阵的leading-dimension,因为是列优先,所以是行数3
                 tau.data());  // 中间变换因子,为上一步sgeqrf求得的结果
  // 5. 打印结果Q矩阵
  print_col_matrix_by_row("Q", Q.data(), M, M);
  // 6. 以上即为QR分解的全过程,下面为重建验证的部分
  vector<float> C(M * N);
  // 7. 使用Q*R重建矩阵A,观察是否等于输入矩阵A
  cblas_sgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, M, N, M, 1.0, Q.data(),
              M, A.data(), M, 1.0, C.data(), M);
  print_col_matrix_by_row("重建A", C.data(), M, N);
  return 0;
}
代码语言:javascript
复制
// 编译链接
g++ -std=c++17 -I/opt/homebrew/opt/openblas/include qr_main.cpp -L/opt/homebrew/opt/openblas/lib -lopenblas -o qr_main
// 执行
./qr_main
//执行结果
R:3x2
-14     -21
0.230769        -175
-0.153846       0.0555556
Q:3x3
-0.857143       0.394286        0.331429
-0.428571       -0.902857       -0.0342857
0.285714        -0.171429       0.942857
重建A:3x2
12.04   -50.9816
5.79692 166.998
-4.18462        24.0524

以上即为完全QR分解的内容,然而在实际的数值计算与工程应用中,经济QR分解因其更高的计算和存储效率而被更广泛的应用,建议您进一步探索和实践。

附录

openblas系列之sgemm

openblas系列之ssyrk

openblas系列之strsm

openblas系列之ssyev

openblas系列之sgesvd

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-02-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 松鼠Agent 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档