numo-linalg-alt 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a27ac7b0136649ad3cb4f65ecaa88eb56327aee77ce92ec189ae51209937dfd
4
- data.tar.gz: d646eb637c82f3602741c6e31a9d18a6b69298c260a5837bfe5cf7c3089fd064
3
+ metadata.gz: bf52b8241dd646725159141067e9e38948c95d89d459f81e8ec5b19a20ce7a69
4
+ data.tar.gz: 286fb08653029ff6bfd745323fd7b8e1165a9d6027bbcf3871bb5333eae8eddd
5
5
  SHA512:
6
- metadata.gz: c89a302f9855730a25ce18fc1b7e81346aecdc6bb22ec4036a1b43dc8981a2e3cb1cfb70249aaee2f2ed3cb641b27058210c5eb6c0e3aef0fc706183d154e7e8
7
- data.tar.gz: 475964589987be96c4a258f4fe01ee63101b5d936e2d6f0e4a198357ccd54361c2d162f87d41c0fe8faaba0badb741f620cb44f9d260c84d33663dd5e27a13f2
6
+ metadata.gz: a7205197001dd99f404a39e63ac7caf6a88fa653ab9487756d3ef69b03c2c9abedc0c3900a1284ae70d41415ddc97f9180e1977d765e3b5f46578a72c67429cf
7
+ data.tar.gz: 3b18b323a4d0873f19c5514ca3856de7651078ef227559f2c55ae9f82f6cb05b4a2bf28958a4c499d7909741e6ad0e58ae53b5821681d6d0f9a363b3719291da
data/CHANGELOG.md CHANGED
@@ -1,4 +1,9 @@
1
- ## [[0.2.0](https://github.com/yoshoku/numo-linalg-alt/compare/d010476...ea50089)]
1
+ ## [[0.3.0](https://github.com/yoshoku/numo-linalg-alt/compare/v0.2.0...v0.3.0)] - 2025-10-06
2
+
3
+ - add `schur`, `cosm`, `sinm`, `orthogonal_procrustes`, and `polar` module functions to Numo::Linalg.
4
+ - fix version specifier for numo-narray-alt.
5
+
6
+ ## [[0.2.0](https://github.com/yoshoku/numo-linalg-alt/compare/d010476...ea50089)] - 2025-09-29
2
7
 
3
8
  - fork from [Numo::TinyLinalg main branch](https://github.com/yoshoku/numo-tiny_linalg/tree/d0104765c560e9664a868b7a3e2f3144bd32c428)
4
9
  - rewrite native extensions with C programming language.
data/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # Numo::Linalg Alternative
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/numo-linalg-alt.svg)](https://badge.fury.io/rb/numo-linalg-alt)
3
4
  [![Build Status](https://github.com/yoshoku/numo-linalg-alt/actions/workflows/main.yml/badge.svg)](https://github.com/yoshoku/numo-linalg-alt/actions/workflows/main.yml)
4
5
  [![BSD 3-Clause License](https://img.shields.io/badge/License-BSD%203--Clause-orange.svg)](https://github.com/yoshoku/numo-linalg-alt/blob/main/LICENSE.txt)
6
+ [![Documentation](https://img.shields.io/badge/api-reference-blue.svg)](https://gemdocs.org/gems/numo-linalg-alt/)
5
7
 
6
8
  Numo::Linalg Alternative (numo-linalg-alt) is an alternative to [Numo::Linalg](https://github.com/ruby-numo/numo-linalg).
7
9
  Unlike Numo::Linalg, numo-linalg-alt depends on [Numo::NArray Alterntive](https://github.com/yoshoku/numo-narray-alt).
@@ -99,7 +101,7 @@ This project is intended to be a safe, welcoming space for collaboration, and co
99
101
 
100
102
  ## Code of Conduct
101
103
 
102
- Everyone interacting in the Numo::TinyLinalg project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/yoshoku/numo-linalg-alt/blob/main/CODE_OF_CONDUCT.md).
104
+ Everyone interacting in the Numo::Linalg Alternative project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/yoshoku/numo-linalg-alt/blob/main/CODE_OF_CONDUCT.md).
103
105
 
104
106
  ## License
105
107
 
@@ -0,0 +1,257 @@
1
+ #include "gees.h"
2
+
3
+ char _get_jobvs(VALUE val) {
4
+ const char jobvs = NUM2CHR(val);
5
+ if (jobvs != 'N' && jobvs != 'V') {
6
+ rb_raise(rb_eArgError, "jobvs must be 'N' or 'V'");
7
+ }
8
+ return jobvs;
9
+ }
10
+
11
+ #define DEF_GEES_OPTION(fLapackFunc, tSelectFunc) \
12
+ struct _gees_option_##fLapackFunc { \
13
+ int matrix_layout; \
14
+ char jobvs; \
15
+ char sort; \
16
+ tSelectFunc select; \
17
+ };
18
+
19
+ #define DEF_GEES_SORT_FUNC(tDType, fLapackFunc) \
20
+ lapack_logical _sort_nil_##fLapackFunc(const tDType* wr, const tDType* wi) { \
21
+ return 0; \
22
+ } \
23
+ lapack_logical _sort_lhp_##fLapackFunc(const tDType* wr, const tDType* wi) { \
24
+ if (*wr < (tDType)0) { \
25
+ return 1; \
26
+ } \
27
+ return 0; \
28
+ } \
29
+ lapack_logical _sort_rhp_##fLapackFunc(const tDType* wr, const tDType* wi) { \
30
+ if (*wr >= (tDType)0) { \
31
+ return 1; \
32
+ } \
33
+ return 0; \
34
+ } \
35
+ lapack_logical _sort_iup_##fLapackFunc(const tDType* wr, const tDType* wi) { \
36
+ tDType magnitude = *wr * *wr + *wi * *wi; \
37
+ if (magnitude <= (tDType)1) { \
38
+ return 1; \
39
+ } \
40
+ return 0; \
41
+ } \
42
+ lapack_logical _sort_ouc_##fLapackFunc(const tDType* wr, const tDType* wi) { \
43
+ tDType magnitude = *wr * *wr + *wi * *wi; \
44
+ if (magnitude > (tDType)1) { \
45
+ return 1; \
46
+ } \
47
+ return 0; \
48
+ }
49
+
50
+ #define DEF_GEES_SORT_FUNC_COMPLEX(tDType, tElType, fLapackRealFunc, fLapackImagFunc, fLapackFunc) \
51
+ lapack_logical _sort_nil_##fLapackFunc(const tDType* w) { \
52
+ return 0; \
53
+ } \
54
+ lapack_logical _sort_lhp_##fLapackFunc(const tDType* w) { \
55
+ if (fLapackRealFunc(*w) < 0.0) { \
56
+ return 1; \
57
+ } \
58
+ return 0; \
59
+ } \
60
+ lapack_logical _sort_rhp_##fLapackFunc(const tDType* w) { \
61
+ if (fLapackRealFunc(*w) >= 0.0) { \
62
+ return 1; \
63
+ } \
64
+ return 0; \
65
+ } \
66
+ lapack_logical _sort_iup_##fLapackFunc(const tDType* w) { \
67
+ tElType real = fLapackRealFunc(*w); \
68
+ tElType imag = fLapackImagFunc(*w); \
69
+ tElType magnitude = real * real + imag * imag; \
70
+ if (magnitude <= (tElType)1.0) { \
71
+ return 1; \
72
+ } \
73
+ return 0; \
74
+ } \
75
+ lapack_logical _sort_ouc_##fLapackFunc(const tDType* w) { \
76
+ tElType real = fLapackRealFunc(*w); \
77
+ tElType imag = fLapackImagFunc(*w); \
78
+ tElType magnitude = real * real + imag * imag; \
79
+ if (magnitude > (tElType)1.0) { \
80
+ return 1; \
81
+ } \
82
+ return 0; \
83
+ }
84
+
85
+ #define DEF_LINALG_FUNC(tDType, tNAryClass, fLapackFunc) \
86
+ static void _iter_##fLapackFunc(na_loop_t* const lp) { \
87
+ tDType* a = (tDType*)(NDL_PTR(lp, 0)); \
88
+ tDType* wr = (tDType*)(NDL_PTR(lp, 1)); \
89
+ tDType* wi = (tDType*)(NDL_PTR(lp, 2)); \
90
+ tDType* vs = (tDType*)(NDL_PTR(lp, 3)); \
91
+ int* sdim = (int*)(NDL_PTR(lp, 4)); \
92
+ int* info = (int*)(NDL_PTR(lp, 5)); \
93
+ struct _gees_option_##fLapackFunc* opt = (struct _gees_option_##fLapackFunc*)(lp->opt_ptr); \
94
+ const lapack_int n = (lapack_int)(opt->matrix_layout == LAPACK_ROW_MAJOR ? NDL_SHAPE(lp, 0)[0] : NDL_SHAPE(lp, 0)[1]); \
95
+ const lapack_int lda = n; \
96
+ const lapack_int ldvs = (opt->jobvs == 'N') ? 1 : n; \
97
+ lapack_int s = 0; \
98
+ lapack_int i = LAPACKE_##fLapackFunc(opt->matrix_layout, opt->jobvs, opt->sort, opt->select, n, a, lda, &s, wr, wi, vs, ldvs); \
99
+ *sdim = (int)s; \
100
+ *info = (int)i; \
101
+ } \
102
+ \
103
+ static VALUE _linalg_lapack_##fLapackFunc(int argc, VALUE* argv, VALUE self) { \
104
+ VALUE a_vnary = Qnil; \
105
+ VALUE kw_args = Qnil; \
106
+ rb_scan_args(argc, argv, "1:", &a_vnary, &kw_args); \
107
+ ID kw_table[3] = { rb_intern("order"), rb_intern("jobvs"), rb_intern("sort") }; \
108
+ VALUE kw_values[3] = { Qundef, Qundef, Qundef }; \
109
+ rb_get_kwargs(kw_args, kw_table, 0, 3, kw_values); \
110
+ const int matrix_layout = kw_values[0] != Qundef ? get_matrix_layout(kw_values[0]) : LAPACK_ROW_MAJOR; \
111
+ const char jobvs = kw_values[1] != Qundef ? _get_jobvs(kw_values[1]) : 'V'; \
112
+ VALUE sort_val = kw_values[2] != Qundef ? kw_values[2] : Qnil; \
113
+ const char sort_ch = NIL_P(sort_val) ? 'N' : 'S'; \
114
+ \
115
+ if (CLASS_OF(a_vnary) != tNAryClass) { \
116
+ a_vnary = rb_funcall(tNAryClass, rb_intern("cast"), 1, a_vnary); \
117
+ } \
118
+ if (!RTEST(nary_check_contiguous(a_vnary))) { \
119
+ a_vnary = nary_dup(a_vnary); \
120
+ } \
121
+ \
122
+ narray_t* a_nary = NULL; \
123
+ GetNArray(a_vnary, a_nary); \
124
+ if (NA_NDIM(a_nary) != 2) { \
125
+ rb_raise(rb_eArgError, "input array must be 2-dimensional array"); \
126
+ return Qnil; \
127
+ } \
128
+ \
129
+ size_t n = matrix_layout == LAPACK_ROW_MAJOR ? NA_SHAPE(a_nary)[0] : NA_SHAPE(a_nary)[1]; \
130
+ size_t shape_wr[1] = { n }; \
131
+ size_t shape_wi[1] = { n }; \
132
+ size_t shape_vs[2] = { n, jobvs == 'N' ? 1 : n }; \
133
+ ndfunc_arg_in_t ain[1] = { { OVERWRITE, 2 } }; \
134
+ ndfunc_arg_out_t aout[5] = { { tNAryClass, 1, shape_wr }, { tNAryClass, 1, shape_wi }, { tNAryClass, 2, shape_vs }, { numo_cInt32, 0 }, { numo_cInt32, 0 } }; \
135
+ ndfunc_t ndf = { _iter_##fLapackFunc, NO_LOOP | NDF_EXTRACT, 1, 5, ain, aout }; \
136
+ struct _gees_option_##fLapackFunc opt = { matrix_layout, jobvs, sort_ch, NULL }; \
137
+ const char* sort_str = NIL_P(sort_val) ? "" : StringValueCStr(sort_val); \
138
+ if (NIL_P(sort_val)) { \
139
+ opt.select = _sort_nil_##fLapackFunc; \
140
+ } else if (strcmp(sort_str, "lhp") == 0) { \
141
+ opt.select = _sort_lhp_##fLapackFunc; \
142
+ } else if (strcmp(sort_str, "rhp") == 0) { \
143
+ opt.select = _sort_rhp_##fLapackFunc; \
144
+ } else if (strcmp(sort_str, "iup") == 0) { \
145
+ opt.select = _sort_iup_##fLapackFunc; \
146
+ } else if (strcmp(sort_str, "ouc") == 0) { \
147
+ opt.select = _sort_ouc_##fLapackFunc; \
148
+ } else { \
149
+ rb_raise(rb_eArgError, "invalid value for sort option"); \
150
+ return Qnil; \
151
+ } \
152
+ VALUE ret = na_ndloop3(&ndf, &opt, 1, a_vnary); \
153
+ \
154
+ RB_GC_GUARD(sort_val); \
155
+ RB_GC_GUARD(a_vnary); \
156
+ return ret; \
157
+ }
158
+
159
+ #define DEF_LINALG_FUNC_COMPLEX(tDType, tNAryClass, fLapackFunc) \
160
+ static void _iter_##fLapackFunc(na_loop_t* const lp) { \
161
+ tDType* a = (tDType*)(NDL_PTR(lp, 0)); \
162
+ tDType* w = (tDType*)(NDL_PTR(lp, 1)); \
163
+ tDType* vs = (tDType*)(NDL_PTR(lp, 2)); \
164
+ int* sdim = (int*)(NDL_PTR(lp, 3)); \
165
+ int* info = (int*)(NDL_PTR(lp, 4)); \
166
+ struct _gees_option_##fLapackFunc* opt = (struct _gees_option_##fLapackFunc*)(lp->opt_ptr); \
167
+ const lapack_int n = (lapack_int)(opt->matrix_layout == LAPACK_ROW_MAJOR ? NDL_SHAPE(lp, 0)[0] : NDL_SHAPE(lp, 0)[1]); \
168
+ const lapack_int lda = n; \
169
+ const lapack_int ldvs = (opt->jobvs == 'N') ? 1 : n; \
170
+ lapack_int s = 0; \
171
+ lapack_int i = LAPACKE_##fLapackFunc(opt->matrix_layout, opt->jobvs, opt->sort, opt->select, n, a, lda, &s, w, vs, ldvs); \
172
+ *sdim = (int)s; \
173
+ *info = (int)i; \
174
+ } \
175
+ \
176
+ static VALUE _linalg_lapack_##fLapackFunc(int argc, VALUE* argv, VALUE self) { \
177
+ VALUE a_vnary = Qnil; \
178
+ VALUE kw_args = Qnil; \
179
+ rb_scan_args(argc, argv, "1:", &a_vnary, &kw_args); \
180
+ ID kw_table[3] = { rb_intern("order"), rb_intern("jobvs"), rb_intern("sort") }; \
181
+ VALUE kw_values[3] = { Qundef, Qundef, Qundef }; \
182
+ rb_get_kwargs(kw_args, kw_table, 0, 3, kw_values); \
183
+ const int matrix_layout = kw_values[0] != Qundef ? get_matrix_layout(kw_values[0]) : LAPACK_ROW_MAJOR; \
184
+ const char jobvs = kw_values[1] != Qundef ? _get_jobvs(kw_values[1]) : 'V'; \
185
+ VALUE sort_val = kw_values[2] != Qundef ? kw_values[2] : Qnil; \
186
+ const char sort_ch = NIL_P(sort_val) ? 'N' : 'S'; \
187
+ \
188
+ if (CLASS_OF(a_vnary) != tNAryClass) { \
189
+ a_vnary = rb_funcall(tNAryClass, rb_intern("cast"), 1, a_vnary); \
190
+ } \
191
+ if (!RTEST(nary_check_contiguous(a_vnary))) { \
192
+ a_vnary = nary_dup(a_vnary); \
193
+ } \
194
+ \
195
+ narray_t* a_nary = NULL; \
196
+ GetNArray(a_vnary, a_nary); \
197
+ if (NA_NDIM(a_nary) != 2) { \
198
+ rb_raise(rb_eArgError, "input array must be 2-dimensional array"); \
199
+ return Qnil; \
200
+ } \
201
+ \
202
+ size_t n = matrix_layout == LAPACK_ROW_MAJOR ? NA_SHAPE(a_nary)[0] : NA_SHAPE(a_nary)[1]; \
203
+ size_t shape_w[1] = { n }; \
204
+ size_t shape_vs[2] = { n, jobvs == 'N' ? 1 : n }; \
205
+ ndfunc_arg_in_t ain[1] = { { OVERWRITE, 2 } }; \
206
+ ndfunc_arg_out_t aout[4] = { { tNAryClass, 1, shape_w }, { tNAryClass, 2, shape_vs }, { numo_cInt32, 0 }, { numo_cInt32, 0 } }; \
207
+ ndfunc_t ndf = { _iter_##fLapackFunc, NO_LOOP | NDF_EXTRACT, 1, 4, ain, aout }; \
208
+ struct _gees_option_##fLapackFunc opt = { matrix_layout, jobvs, sort_ch, NULL }; \
209
+ const char* sort_str = NIL_P(sort_val) ? "" : StringValueCStr(sort_val); \
210
+ if (NIL_P(sort_val)) { \
211
+ opt.select = _sort_nil_##fLapackFunc; \
212
+ } else if (strcmp(sort_str, "lhp") == 0) { \
213
+ opt.select = _sort_lhp_##fLapackFunc; \
214
+ } else if (strcmp(sort_str, "rhp") == 0) { \
215
+ opt.select = _sort_rhp_##fLapackFunc; \
216
+ } else if (strcmp(sort_str, "iup") == 0) { \
217
+ opt.select = _sort_iup_##fLapackFunc; \
218
+ } else if (strcmp(sort_str, "ouc") == 0) { \
219
+ opt.select = _sort_ouc_##fLapackFunc; \
220
+ } else { \
221
+ rb_raise(rb_eArgError, "invalid value for sort option"); \
222
+ return Qnil; \
223
+ } \
224
+ VALUE ret = na_ndloop3(&ndf, &opt, 1, a_vnary); \
225
+ \
226
+ RB_GC_GUARD(sort_val); \
227
+ RB_GC_GUARD(a_vnary); \
228
+ return ret; \
229
+ }
230
+
231
+ DEF_GEES_OPTION(dgees, LAPACK_D_SELECT2)
232
+ DEF_GEES_OPTION(sgees, LAPACK_S_SELECT2)
233
+ DEF_GEES_OPTION(zgees, LAPACK_Z_SELECT1)
234
+ DEF_GEES_OPTION(cgees, LAPACK_C_SELECT1)
235
+
236
+ DEF_GEES_SORT_FUNC(double, dgees)
237
+ DEF_GEES_SORT_FUNC(float, sgees)
238
+ DEF_GEES_SORT_FUNC_COMPLEX(lapack_complex_double, double, lapack_complex_double_real, lapack_complex_double_imag, zgees)
239
+ DEF_GEES_SORT_FUNC_COMPLEX(lapack_complex_float, float, lapack_complex_float_real, lapack_complex_float_imag, cgees)
240
+
241
+ DEF_LINALG_FUNC(double, numo_cDFloat, dgees)
242
+ DEF_LINALG_FUNC(float, numo_cSFloat, sgees)
243
+ DEF_LINALG_FUNC_COMPLEX(lapack_complex_double, numo_cDComplex, zgees)
244
+ DEF_LINALG_FUNC_COMPLEX(lapack_complex_float, numo_cSComplex, cgees)
245
+
246
+ #undef DEF_GEES_OPTION
247
+ #undef DEF_GEES_SORT_FUNC
248
+ #undef DEF_GEES_SORT_FUNC_COMPLEX
249
+ #undef DEF_LINALG_FUNC
250
+ #undef DEF_LINALG_FUNC_COMPLEX
251
+
252
+ void define_linalg_lapack_gees(VALUE mLapack) {
253
+ rb_define_module_function(mLapack, "dgees", RUBY_METHOD_FUNC(_linalg_lapack_dgees), -1);
254
+ rb_define_module_function(mLapack, "sgees", RUBY_METHOD_FUNC(_linalg_lapack_sgees), -1);
255
+ rb_define_module_function(mLapack, "zgees", RUBY_METHOD_FUNC(_linalg_lapack_zgees), -1);
256
+ rb_define_module_function(mLapack, "cgees", RUBY_METHOD_FUNC(_linalg_lapack_cgees), -1);
257
+ }
@@ -0,0 +1,15 @@
1
+ #ifndef NUMO_LINALG_ALT_LAPACK_GEES_H
2
+ #define NUMO_LINALG_ALT_LAPACK_GEES_H 1
3
+
4
+ #include <lapacke.h>
5
+
6
+ #include <ruby.h>
7
+
8
+ #include <numo/narray.h>
9
+ #include <numo/template.h>
10
+
11
+ #include "../util.h"
12
+
13
+ void define_linalg_lapack_gees(VALUE mLapack);
14
+
15
+ #endif /* NUMO_LINALG_ALT_LAPACK_GEES_H */
@@ -257,6 +257,7 @@ void Init_linalg(void) {
257
257
  define_linalg_lapack_geqrf(rb_mLinalgLapack);
258
258
  define_linalg_lapack_orgqr(rb_mLinalgLapack);
259
259
  define_linalg_lapack_ungqr(rb_mLinalgLapack);
260
+ define_linalg_lapack_gees(rb_mLinalgLapack);
260
261
  define_linalg_lapack_geev(rb_mLinalgLapack);
261
262
  define_linalg_lapack_gesv(rb_mLinalgLapack);
262
263
  define_linalg_lapack_gesvd(rb_mLinalgLapack);
@@ -51,6 +51,7 @@
51
51
  #include "blas/gemv.h"
52
52
  #include "blas/nrm2.h"
53
53
 
54
+ #include "lapack/gees.h"
54
55
  #include "lapack/geev.h"
55
56
  #include "lapack/gelsd.h"
56
57
  #include "lapack/geqrf.h"
@@ -5,6 +5,6 @@ module Numo
5
5
  # Numo::Linalg Alternative (numo-linalg-alt) is an alternative to Numo::Linalg.
6
6
  module Linalg
7
7
  # The version of numo-linalg-alt you install.
8
- VERSION = '0.2.0'
8
+ VERSION = '0.3.0'
9
9
  end
10
10
  end
data/lib/numo/linalg.rb CHANGED
@@ -444,6 +444,54 @@ module Numo
444
444
  u.dot(vh[0...rank, true]).conj.transpose
445
445
  end
446
446
 
447
+ # Computes the polar decomposition of a matrix.
448
+ #
449
+ # https://en.wikipedia.org/wiki/Polar_decomposition
450
+ #
451
+ # @example
452
+ # require 'numo/linalg'
453
+ #
454
+ # a = Numo::DFloat[[0.5, 1, 2], [1.5, 3, 4]]
455
+ # u, p = Numo::Linalg.polar(a)
456
+ # pp u.dot(p)
457
+ # # =>
458
+ # # Numo::DFloat#shape=[2,3]
459
+ # # [[0.5, 1, 2],
460
+ # # [1.5, 3, 4]]
461
+ # pp u.dot(u.transpose)
462
+ # # =>
463
+ # # Numo::DFloat#shape=[2,2]
464
+ # # [[1, -1.68043e-16],
465
+ # # [-1.68043e-16, 1]]
466
+ #
467
+ # u, p = Numo::Linalg.polar(a, side: 'left')
468
+ # pp p.dot(u)
469
+ # # =>
470
+ # # Numo::DFloat#shape=[2,3]
471
+ # # [[0.5, 1, 2],
472
+ # # [1.5, 3, 4]]
473
+ #
474
+ # @param a [Numo::NArray] The m-by-n matrix to be decomposed.
475
+ # @param side [String] The side of polar decomposition ('right' or 'left').
476
+ # @return [Array<Numo::NArray>] The unitary matrix `U` and the positive-semidefinite Hermitian matrix `P`
477
+ # such that `A = U * P` if side='right', or `A = P * U` if side='left'.
478
+ def polar(a, side: 'right')
479
+ raise Numo::NArray::ShapeError, 'input array a must be 2-dimensional' if a.ndim != 2
480
+ raise ArugumentError, "invalid side: #{side}" unless %w[left right].include?(side)
481
+
482
+ bchr = blas_char(a)
483
+ raise ArgumentError, "invalid array type: #{a.class}" if bchr == 'n'
484
+
485
+ s, w, vh = svd(a, driver: 'svd', job: 'S')
486
+ u = w.dot(vh)
487
+ p_mat = if side == 'right'
488
+ vh.transpose.conj.dot(s.diag).dot(vh)
489
+ else
490
+ w.dot(s.diag).dot(w.transpose.conj)
491
+ end
492
+ [u, p_mat]
493
+ end
494
+
447
495
  # Computes the QR decomposition of a matrix.
448
496
  #
449
497
  # @example
@@ -512,6 +560,66 @@ module Numo
512
560
  [q, r]
513
561
  end
514
562
 
563
+ # Computes the Schur decomposition of a square matrix.
564
+ # The Schur decomposition is given by `A = Z * T * Z^H`,
565
+ # where `A` is the input matrix, `Z` is a unitary matrix,
566
+ # and `T` is an upper triangular matrix (or quasi-upper triangular matrix in real case).
567
+ #
568
+ # @example
569
+ # require 'numo/linalg'
570
+ #
571
+ # a = Numo::DFloat[[0, 2, 3], [4, 5, 6], [7, 8, 9]]
572
+ # t, z, sdim = Numo::Linalg.schur(a)
573
+ # pp t
574
+ # # =>
575
+ # # Numo::DFloat#shape=[3,3]
576
+ # # [[16.0104, 4.81155, 0.920982],
577
+ # # [0, -1.91242, 0.0274406],
578
+ # # [0, 0, -0.0979794]]
579
+ # pp z
580
+ # # =>
581
+ # # Numo::DFloat#shape=[3,3]
582
+ # # [[-0.219668, -0.94667, 0.235716],
583
+ # # [-0.527141, -0.0881306, -0.845195],
584
+ # # [-0.820895, 0.309918, 0.479669]]
585
+ # pp sdim
586
+ # # => 0
587
+ # pp (a - z.dot(t).dot(z.transpose)).abs.max
588
+ # # => 1.0658141036401503e-14
589
+ #
590
+ # @param a [Numo::NArray] The n-by-n square matrix.
591
+ # @param sort [String/Nil] The option for sorting eigenvalues ('lhp', 'rhp', 'iuc', 'ouc', or nil).
592
+ # - 'lhp': eigenvalue.real < 0
593
+ # - 'rhp': eigenvalue.real >= 0
594
+ # - 'iuc': eigenvalue.abs <= 1
595
+ # - 'ouc': eigenvalue.abs > 1
596
+ # @return [Array<Numo::NArray, Numo::NArray, Integer>] The Schur form `T`, the unitary matrix `Z`,
597
+ # and the number of eigenvalues for which the sorting condition is true.
598
+ def schur(a, sort: nil)
599
+ raise Numo::NArray::ShapeError, 'input array a must be 2-dimensional' if a.ndim != 2
600
+ raise Numo::NArray::ShapeError, 'input array a must be square' if a.shape[0] != a.shape[1]
601
+ raise ArgumentError, "invalid sort: #{sort}" unless sort.nil? || %w[lhp rhp iuc ouc].include?(sort)
602
+
603
+ bchr = blas_char(a)
604
+ raise ArgumentError, "invalid array type: #{a.class}" if bchr == 'n'
605
+
606
+ fnc = :"#{bchr}gees"
607
+ b = a.dup
608
+ if %w[d s].include?(bchr)
609
+ _wr, _wi, v, sdim, info = Numo::Linalg::Lapack.send(fnc, b, jobvs: 'V', sort: sort)
610
+ else
611
+ _w, v, sdim, info = Numo::Linalg::Lapack.send(fnc, b, jobvs: 'V', sort: sort)
612
+ end
613
+
614
+ n = a.shape[0]
615
+ raise "the #{-info}-th argument of #{fnc} had illegal value" if info.negative?
616
+ raise 'the QR algorithm failed to compute all the eigenvalues.' if info.positive? && info <= n
617
+ raise 'the eigenvalues could not be reordered.' if info == n + 1
618
+ raise 'after reordering, roundoff changed values of some complex eigenvalues.' if info == n + 2
619
+
620
+ [b, v, sdim]
621
+ end
622
+
515
623
  # Solves linear equation `A * x = b` or `A * X = B` for `x` from square matrix `A`.
516
624
  #
517
625
  # @example
@@ -921,6 +1029,46 @@ module Numo
921
1029
  c
922
1030
  end
923
1031
 
1032
+ # Computes the orthogonal Procrustes problem.
1033
+ #
1034
+ # https://en.wikipedia.org/wiki/Orthogonal_Procrustes_problem
1035
+ #
1036
+ # @example
1037
+ # require 'numo/linalg'
1038
+ #
1039
+ # a = Numo::DFloat[[2, 0, 1], [-2, 0, 0]]
1040
+ # b = a.fliplr
1041
+ # r, scale = Numo::Linalg.orthogonal_procrustes(a, b)
1042
+ #
1043
+ # pp b
1044
+ # # =>
1045
+ # # Numo::DFloat(view)#shape=[2,3]
1046
+ # # [[1, 0, 2],
1047
+ # # [0, 0, -2]]
1048
+ # pp a.dot(r)
1049
+ # # =>
1050
+ # # Numo::DFloat#shape=[2,3]
1051
+ # # [[1, 0, 2],
1052
+ # # [1.58669e-16, 0, -2]]
1053
+ # pp (b - a.dot(r)).abs.max
1054
+ # # =>
1055
+ # # 2.220446049250313e-16
1056
+ #
1057
+ # @param a [Numo::NArray] The first input matrix.
1058
+ # @param b [Numo::NArray] The second input matrix.
1059
+ # @return [Array<Numo::NArray, Float>] The orthogonal matrix `R` and the scale factor `scale`.
1060
+ def orthogonal_procrustes(a, b)
1061
+ raise Numo::NArray::ShapeError, 'input array a must be 2-dimensional' if a.ndim != 2
1062
+ raise Numo::NArray::ShapeError, 'input array b must be 2-dimensional' if b.ndim != 2
1063
+ raise Numo::NArray::ShapeError, "incompatible dimensions: a.shape = #{a.shape} != b.shape = #{b.shape}" if a.shape != b.shape
1064
+
1065
+ m = b.transpose.dot(a.conj).transpose
1066
+ s, u, vt = svd(m, driver: 'svd', job: 'S')
1067
+ r = u.dot(vt)
1068
+ scale = s.sum
1069
+ [r, scale]
1070
+ end
1071
+
924
1072
  # Computes the eigenvalues and right and/or left eigenvectors of a general square matrix.
925
1073
  #
926
1074
  # @example
@@ -1195,6 +1343,44 @@ module Numo
1195
1343
  a_expm
1196
1344
  end
1197
1345
 
1346
+ # Computes the matrix sine using the matrix exponential.
1347
+ #
1348
+ # @param a [Numo::NArray] The n-by-n square matrix.
1349
+ # @return [Numo::NArray] The matrix sine of `a`.
1350
+ def sinm(a)
1351
+ raise Numo::NArray::ShapeError, 'input array a must be 2-dimensional' if a.ndim != 2
1352
+ raise Numo::NArray::ShapeError, 'input array a must be square' if a.shape[0] != a.shape[1]
1353
+
1354
+ bchr = blas_char(a)
1355
+ raise ArgumentError, "invalid array type: #{a.class}" if bchr == 'n'
1356
+
1357
+ b = a * 1.0i
1358
+ if %w[z c].include?(bchr)
1359
+ -0.5i * (expm(b) - expm(-b))
1360
+ else
1361
+ expm(b).imag
1362
+ end
1363
+ end
1364
+
1365
+ # Computes the matrix cosine using the matrix exponential.
1366
+ #
1367
+ # @param a [Numo::NArray] The n-by-n square matrix.
1368
+ # @return [Numo::NArray] The matrix cosine of `a`.
1369
+ def cosm(a)
1370
+ raise Numo::NArray::ShapeError, 'input array a must be 2-dimensional' if a.ndim != 2
1371
+ raise Numo::NArray::ShapeError, 'input array a must be square' if a.shape[0] != a.shape[1]
1372
+
1373
+ bchr = blas_char(a)
1374
+ raise ArgumentError, "invalid array type: #{a.class}" if bchr == 'n'
1375
+
1376
+ b = a * 1.0i
1377
+ if %w[z c].include?(bchr)
1378
+ 0.5 * (expm(b) + expm(-b))
1379
+ else
1380
+ expm(b).real
1381
+ end
1382
+ end
1383
+
1198
1384
  # Computes the inverse of a matrix using its LU decomposition.
1199
1385
  #
1200
1386
  # @param lu [Numo::NArray] The LU decomposition of the n-by-n matrix `A`.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: numo-linalg-alt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yoshoku
@@ -13,14 +13,14 @@ dependencies:
13
13
  name: numo-narray-alt
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - ">="
16
+ - - "~>"
17
17
  - !ruby/object:Gem::Version
18
18
  version: 0.9.3
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
- - - ">="
23
+ - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: 0.9.3
26
26
  description: |
@@ -50,6 +50,8 @@ files:
50
50
  - ext/numo/linalg/converter.c
51
51
  - ext/numo/linalg/converter.h
52
52
  - ext/numo/linalg/extconf.rb
53
+ - ext/numo/linalg/lapack/gees.c
54
+ - ext/numo/linalg/lapack/gees.h
53
55
  - ext/numo/linalg/lapack/geev.c
54
56
  - ext/numo/linalg/lapack/geev.h
55
57
  - ext/numo/linalg/lapack/gelsd.c
@@ -124,7 +126,7 @@ metadata:
124
126
  homepage_uri: https://github.com/yoshoku/numo-linalg-alt
125
127
  source_code_uri: https://github.com/yoshoku/numo-linalg-alt
126
128
  changelog_uri: https://github.com/yoshoku/numo-linalg-alt/blob/main/CHANGELOG.md
127
- documentation_uri: https://gemdocs.org/gems/numo-linalg-alt/0.2.0/
129
+ documentation_uri: https://gemdocs.org/gems/numo-linalg-alt/0.3.0/
128
130
  rubygems_mfa_required: 'true'
129
131
  rdoc_options: []
130
132
  require_paths: