Skip to content

Magicl NG #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 121 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from 105 commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
b6cafac
Add initial version of magicl high-level rewrite
Sep 12, 2019
46815b3
Add *derive-function-types* to asdf project
Sep 18, 2019
9ac5afe
Add random, fix multiplication, add lapack, make things work
Sep 18, 2019
89b3ada
Fix idenetity typo
Sep 18, 2019
55bb800
Fix empty and deye documentation and fix assert-square-matrix printin…
Sep 18, 2019
1a3f550
Remove FASL in high-level dir (Why was that there?)
Sep 18, 2019
e8c30b7
Fix indentation in matrix.lisp and clean up trace
Sep 19, 2019
d379d37
Add macro to generate blas mult boilerplate
Sep 19, 2019
f6ea27e
Remove old version of matrix/double-float
Sep 19, 2019
35fcd3c
Add lapack macros to make mult work
Sep 19, 2019
708ac46
Fix from-*-major-index and add tests for util file
Sep 26, 2019
5d5b183
Move lapack method definitions into macros for ease of use
Sep 26, 2019
d6faa05
Add square restriction to qr/ql/rq/lq
Oct 13, 2019
4fbc577
Remove magicl from :use on packages to avoid shadowing errors
Oct 13, 2019
6373563
Add support for calling PPRINT-MATRIX within FORMAT
Oct 13, 2019
c73430e
Add distribution as function to RAND and add tests
Oct 13, 2019
4399d78
Add lapack inverse to high level and finish updating examples to new …
Oct 27, 2019
f6a69fb
Remove debug message from lapack-inv
Oct 27, 2019
03365ca
Update TEST-RANDOM-UNITARY and TEST-LOGM to new interface
Oct 27, 2019
b4c14d0
Fix TEST-KRON
Oct 27, 2019
5558beb
Gave it some throught. It should work.
Oct 27, 2019
83a5c47
Add p-norm to vector type
Oct 27, 2019
60a3e4f
Add slice to ABSTRACT-TENSOR
Oct 31, 2019
6f96e17
Remove TODO regarding if it makes sense. It makes sense.
Oct 31, 2019
6b656aa
Fix tests not calling all the tests
Nov 3, 2019
03986ad
Fix constant redefinition in magicl-tests caused by incorrect test pr…
Nov 3, 2019
ec93aa7
Extract function for type inferring in tensor constructors
Nov 3, 2019
4eb5b03
Clean up TODOs in high-level
Nov 3, 2019
77b8297
Reduce oddity in abstract-tensor-tests and include dim=1 case
Nov 3, 2019
46e75ff
Fix TEST-TENSOR-SHAPE and TEST-TENSOR-RANK with more advanced loops
Nov 3, 2019
263d2b5
Remove unneeded multiplication of shape in FROM-DIAG constructor
Nov 3, 2019
71f82ff
Update high-level docstrings to be more uniform and descriptive and a…
Nov 5, 2019
c259a7f
Implement VALID-INDEX-P properly and use in high-level
Nov 5, 2019
e3e7db5
Fix teh typo and bad indentation in high-level-tests
Nov 5, 2019
2815c4c
Fix indentation in INFER-TENSOR-TYPE
Nov 5, 2019
5d023eb
Fix VALID-INDEX-P to allow for not specifying shape
Nov 5, 2019
9cdd3f5
Make map and into functions more readable
Nov 5, 2019
47503eb
Change high-level constructors to functions from generic functions
Nov 5, 2019
d7f9f6d
Make DEFVECTOR/DEFMATRIX macros more expandable
Nov 5, 2019
ee315a7
Add HERMITIAN-EIG to new high-level interface
Nov 5, 2019
915df12
Fix appleby's nitpicks
Nov 7, 2019
0158177
Add policy-cond for performance when speed > safety and directly cons…
Dec 18, 2019
20969eb
Switch from CLOS to structs for tensor objects
Dec 20, 2019
8e8b423
Remove freeze on tensor struct causing type error
Dec 20, 2019
ac08848
Fix pretty printing for matrix and tensor types and remove old methods
Dec 20, 2019
8505e1a
Remove accidentally committed log files
Dec 20, 2019
c0affa9
Add (SETF SHAPE) and optimize matrix TREF
Dec 20, 2019
8854023
Remove warnings from unused arguments and load order
Dec 30, 2019
41825f1
Add basic documentation for high-level interface
Dec 30, 2019
2f31528
Fix bug in transpose on matrix with dimension of 1
Dec 30, 2019
d98481e
Add integer printing to matrix and vector pretty printers
Dec 30, 2019
0b1f445
Change from dynamic to static symbols in INFER-TENSOR-TYPE and move t…
Dec 31, 2019
64e43b1
Add CAST, a replacement for CHANGE-CLASS on tensors
Dec 31, 2019
c9d7590
Clean up util and comments
Dec 31, 2019
39e55f7
Add subtypes and straggling symbols to package
Dec 31, 2019
b236a1a
Add reduced svd interface and tests (#65)
Dec 31, 2019
00b4737
Add CSD-2X2-BASIC (#67)
Jan 1, 2020
f13ccd3
Use use-expectations in the expected way
Jan 1, 2020
c9099f5
Change ABSTRACT-TENSOR to not allow for construction or copying
Jan 2, 2020
d8e95e7
Move magicl high-level into module in ASDF
Jan 2, 2020
13ab91a
Clean up documentation for ABSTRACT-TENSOR
Jan 2, 2020
53b9b04
Replace POLICY-IF with WITH-EXPECTATIONS in constructors
Jan 2, 2020
a60a9f5
Fix mistake in abstract-tensor docstring
Jan 2, 2020
4d40da0
Use COPY-SEQ for copying tensor storage and add copiers to vector
Jan 2, 2020
3820678
Remove funky comma in ASSERT-SQUARE-SHAPE
Jan 2, 2020
7520aa2
Move lapack bindings to lapack-bindings.lisp and switch to functions …
Jan 2, 2020
453df93
Update docstrings for DEFTENSOR, DEFMATRIX, DEFVECTOR, and DEFCOMPATIBLE
Jan 2, 2020
cb99342
Fix other library equivalents table errors
colescott Jan 2, 2020
2c26e9c
Change def- to generate- in lapack-templates to reflect change from m…
Jan 2, 2020
79811ce
Add numpy rq to high-level doc
Jan 2, 2020
345d758
Change terminology regarding indexing and number of dimensions of ten…
Jan 3, 2020
aaa000e
Remove LAYOUT arg from INTO! and FOREACH in abstract tensor interface
Jan 3, 2020
eb653cf
Add ZEROS and ONES constructors for convenience
Jan 3, 2020
bb867a7
Remove square requirement from DEYE constructor
Jan 3, 2020
d3a480b
Remove ORDER 2 requirement from DEYE
Jan 3, 2020
4df0b7c
Rename DEYE to EYE
Jan 3, 2020
39e68ed
Add TRIL and TRIU synonyms and change DAGGER(!) to synonyms
Jan 3, 2020
f3efe6f
Add TRIL/TRIU to package
Jan 3, 2020
7c40812
Add issue number to ZUNCSD shim
Jan 3, 2020
8525dd8
Replace = with := in LOOPs
Jan 3, 2020
4d119fb
Allow for FIXNUM in EYE shape and remove SHAPE requirement from FROM-…
Jan 3, 2020
631c658
Update tests to refelect change in FROM-DIAG interface
Jan 3, 2020
6e28586
Add BINARY-OPERATOR method and add '.' prefix to element-wise operators
Jan 3, 2020
e5ba944
Update documentation for elementwise operators
Jan 3, 2020
7b87ea8
Remove unwanted argument on FROM-DIAG
Jan 3, 2020
c5d2904
Fix typo, represening how bad I am at speling
Jan 3, 2020
ee2dcc7
Fix RAND docstring to be more documenting
Jan 4, 2020
a59c28f
Allow for multiple tensors in every, any, notevery, notany
Jan 27, 2020
cda9a1e
Fix bug in hermitian-eig caused by copying/resetting layout slot on r…
Jan 27, 2020
7721785
Export slice and mult from high-level
Jan 27, 2020
777eff0
Fix expm returning incorrect layout for result
Jan 27, 2020
e6420b3
Bump version to 0.7.0
Jan 27, 2020
52ac9b7
Symbol case invariance. Drop unused declarations (#74)
jmbr Jan 29, 2020
a11339c
Add optimizations for MATRIX class
Feb 11, 2020
524a087
Update documentation to be more correct
colescott Feb 11, 2020
82d87bd
Change INVERSE -> INV
Feb 11, 2020
d2303a3
Change POLICY-IF to POLICY-COND
Feb 11, 2020
400a40c
Fix LAYOUT->ORDER in high-level docs
Feb 12, 2020
4ed0883
Fix indentation in high-level docs
Feb 12, 2020
e0c0860
Fix deye->eye in high-level docs
Feb 12, 2020
4b17a3b
Add check for valid index to tref and setf tref in vector and tensor …
Feb 12, 2020
c27a0fa
Change default tensor type to var to allow for rebinding
Feb 12, 2020
4b97964
Remove unneeded variable in hermitian-eig
Feb 12, 2020
fb1162b
Force square shape for upper and lower triangular of matrix
Feb 12, 2020
a765019
Update element-wise exponentiation in high-level docs to be consistent
Feb 12, 2020
923f016
Update examples.lisp
colescott Feb 17, 2020
2df9817
Remove layout from ARANGE constructor
Feb 17, 2020
5fecbc2
Fix tensor equality errors and add corresponding tests
Feb 17, 2020
339e7d2
Add equality for float vectors and fix error in high-level tests
Feb 17, 2020
415808a
Overhaul p-norm for vectors and add tests
Feb 18, 2020
015e273
Update documentation for numpy hermitian transpose
Feb 18, 2020
cfbd144
Fix p-norm for infinite norm of negative single element vectors
Feb 18, 2020
9c4e1ac
Change float comparison thresholds to variables
Feb 18, 2020
9ab4a0d
Merge #69 from master into feature/magicl-ng
Feb 18, 2020
837f859
Change *-MATRIX-P to functions
Feb 26, 2020
4a1b206
Fix incorrect assertion in p-norm of vector
Feb 26, 2020
7597577
Apply suggestions from code review
colescott Feb 26, 2020
65f6de6
Remove ORTHONORMALIZE methods for matrices (just use QR)
Feb 26, 2020
33973d5
Add check for singular matrices in INV folllowing call to LU
Feb 26, 2020
30e518f
Remove old note about LU requried for INV.
Feb 26, 2020
6d04eb3
Add P-NORM-TYPE and modify vector norm to use type and to disallow ne…
Feb 26, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ You can run the MAGICL tests from your Lisp REPL with:
(asdf:test-system :magicl)
```

## High-level Interface

See [high-level doc](doc/high-level.md).

## Showing Available Functions

Some distributions of a library don't actually provide all of the functions of the reference BLAS and LAPACK. One can look at a summary of available and unavailable functions with the function `magicl:print-availability-report`. By default, it will show all functions and their availability. There are three arguments to fine-tune this behavior:
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"0.6.5"
"0.7.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a major version bump, btw.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for pre-1.0 releases, major/minor doesn't have the same meaning, but generally bumping the minor is treated similarly to bumping the major in a post-1.0 release?

You could argue that magicl should already have been on a post-1.0 release, but that's a different story.

In practice I don't know how many downstream consumers magicl has besides quilc/qvm, so maybe it doesn't matter much, but I would lean towards keeping it pre-1.0.0 for at least a few more releases to give time for the new API to settle and shake out bugs before going to 1.0.

It just seems weird for something so new and not quite "stable" yet to get tagged as the 1.0 release. That is my $0.02 anyway, but I also would not ragequit if we made this 1.0.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't stay 0.x forever. Shaking out bugs in a big release is what we're supposed to be doing now? i.e. what if magicl was already 1.x and we wanted to make this release? You wouldn't then say we should make a minor 1.y release to shake out the bugs and only then do a 2.0? Or would you? Genuine question!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if it's what we supposed to be doing, but the reality is:

  1. It'd be surprising if such massive changes didn't have a few bugs that need shaking out. See @kilimanjaro's comments above from just a quick run-through of the high-level docs.

  2. There are quite a few comments in this PR suggesting API additions and improvements for follow-on PRs, so it doesn't quite seem like the API is that stable yet, which what 1.0 implies to my mind.

I guess my view is that a 1.0 release should be approached asymptotically rather than in a seismic shift.

If magicl already had a 1.0 release, then this would be a 2.0-worthy change for sure, but it hasn't so it feels odd to me to label this the 1.0 release based on the fact that it introduces major breaking changes, rather than basing it on the fact that the API feels in some sense "stable enough to be called 1.0", although the quotes there mean I don't have some clear criteria for what "stable enough" would mean.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that "1.0.0" is overloaded with symbolism. Does semantic versioning just not apply for 0.y.z?

Not that it matters. I don't mind if we go to 0.7.0 rather than 1.0.0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a semver expert, I just play one on TV, but according to their faq pre-1.0 releases are treated differently

https://semver.org/#how-should-i-deal-with-revisions-in-the-0yz-initial-development-phase

Of course, the very next bullet point makes the argument that magicl should probably have already been on a post-1.0 release...

I think one thing we both agree on is that it probably doesn't matter much if quilc and qvm are the only downstream consumers :)

Copy link
Member Author

@colescott colescott Feb 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will be bumping to 1.0.0

edit:
appleby convinced me. It will stick with 0.7.0

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm more or less with appleby on this one as well. Version 1.0.0 feels special because it is special. My only objection to this being 1.0.0 is that we don't have much working experience with the API, outside of this code review, and so it is hard to anticipate what follow-up work will be on the "wish list".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I concur.

83 changes: 83 additions & 0 deletions doc/high-level.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# High Level Bindings

The purpose of the high-level magicl bindings is to allow for MATLAB-like multidimensional arrays in lisp.

## Constructors

The construction of tensors can be done with any of the given constructors. The constructors take a shape and arguments for method of construction.

Tensors are specialized on both the shape and the element type. The class of a tensor will be of the form `$CLASS/$TYPE` (e.g. `MATRIX/DOUBLE-FLOAT`).

| Number of dimensions | Tensor Class |
|----------------------|-----------------|
| 1 | `VECTOR` |
| 2 | `MATRIX` |
| * | `TENSOR` |

| Element Type | Class Suffix |
|--------------------------|------------------------|
| `SINGLE-FLOAT` | `SINGLE-FLOAT` |
| `DOUBLE-FLOAT` | `DOUBLE-FLOAT` |
| `(COMPLEX SINGLE-FLOAT)` | `COMPLEX-SINGLE-FLOAT` |
| `(COMPLEX DOUBLE-FLOAT)` | `COMPLEX-DOUBLE-FLOAT` |
| `(SIGNED-BYTE 32)` | `INT32` |

The type of the elements of the tensor can be specified with the `:type` keyword, or the constructor will attempt to find an appropriate type from the given arguments. The default element type for a tensor is `DOUBLE-FLOAT`.

The layout of the tensor (column-major or row-major) can be specified with the `:layout` keyword. This affects the underlying storage of the tensor and will affect how it carries out operations with LAPACK.


## Other Library Equivalents

This table was adapted largely from the [NumPy Equivalents Table](https://docs.scipy.org/doc/numpy/user/numpy-for-matlab-users.html#linear-algebra-equivalents).

### Basic Accessors

| MAGICL | MATLAB | NumPy | Description |
|----------------|------------|-------------------------|---------------------------------------------------------------|
| `(order a)` | `ndims(a)` | `ndim(a)` or `a.ndim` | Get the number of dimensions of the array. |
| `(size a)` | `numel(a)` | `size(a)` or `a.size` | Get the number of elements of the array. |
| `(shape a)` | `size(a)` | `shape(a)` or `a.shape` | Get the shape of the array. |
| `(tref a 1 4)` | `a(2,5)` | `a[1, 4]` | Get the element in the second row, fifth column of the array. |

### Constructors

| MAGICL | MATLAB | NumPy | Description |
|-------------------------------------------------|--------------------|-----------------------------------|--------------------------------------------------------------------------------------|
| `(from-list '(1d0 2d0 3d0 4d0 5d0 6d0) '(2 3))` | `[ 1 2 3; 4 5 6 ]` | `array([[1.,2.,3.], [4.,5.,6.]])` | Create a 2x3 matrix from given elements. |
| `(zeros '(2 3 4))` or `(const 0d0 '(2 3 4))` | `zeros(2,3,4)` | `zeros((2,3,4))` | Create a 2x3x4 dimensional array of zeroes of double-float element type. |
| `(ones '(3 4)` or `(const 1d0 '(3 4))` | `ones(3,4)` | `ones((3,4))` | Create a 3x4 dimensional array of ones of double-float element type. |
| `(eye 1d0 '(3 3))` | `eye(3)` | `eye(3)` | Create a 3x3 identity array of double-float element type. |
| `(from-diag a)` | `diag(a)` | `diag(a)` | Create a square matrix from the diagonal entries in `a` with zeroes everywhere else. |
| `(rand '(3 4))` | `rand(3,4)` | `random.rand(3,4)` | Create a random 3x4 array. |

### Basic Operations

| MAGICL | MATLAB | NumPy | Description |
|------------|----------|------------------|-----------------------------|
| `(@ a b)` | `a * b` | `a @ b` | Matrix multiplication |
| `(.+ a b)` | `a + b` | `a + b` | Element-wise add |
| `(.- a b)` | `a - b` | `a - b` | Element-wise subtract |
| `(.* a b)` | `a .* b` | `a * b` | Element-wise multiply |
| `(./ a b)` | `a./b` | `a/b` | Element-wise divide |
| `(.^ a b)` | `a.^b` | `np.power(a,b)` | Element-wise exponentiation |

### Linear Algebra

| MAGICL | MATLAB | NumPy | Description |
|---------------------------------------------------------|-------------------|----------------------------------------------------------------|--------------------------------------------|
| `(det a)` | `det(a)` | `linalg.det(a)` | Determinant of matrix |
| `(trace a)` | `trace(a)` | `trace(a)` | Trace (sum of diagonal elements) of matrix |
| `(upper-triangular a)` | `triu(a)` | `triu(a)` | Upper triangular part of matrix |
| `(lower-triangular a)` | `tril(a)` | `tril(a)` | Lower triangular part of matrix |
| `(transpose a)` | `a.'` | `a.transpose()` or `a.T` | Transpose of matrix |
| `(conjugate-transpose a)` or `(dagger a)` | `a'` | `a.conj().transpose()` or `a.conj().T` | Conjugate transpose of matrix |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request: define t and h as synonyms to (respectively) denote the transpose and Hermitian conjugate.

As a matter of fact, the numpy.matrix class has an H attribute that does this.

| `(inv a)` | `inv(a)` | `linalg.inv(a)` | Inverse of matrix |
| `(svd a)` (Returns `(VALUES U SIGMA Vt)`) | `[U,S,V]=svd(a)` | `U, S, Vh = linalg.svd(a), V = Vh.T` | Singular value decomposition of matrix |
| `(eig a)` (Returns `(VALUES EIGENVALUES EIGENVECTORS)`) | `[V,D]=eig(a)` | `D,V = linalg.eig(a)` | Eigenvalues and eigenvectors of matrix |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to only track the real parts of the eigenvalues and eigenvalues. For example, (from-list '(0d0 -1d0 1d0 0d0) '(2 2)) is a 90 degree counterclockwise rotation. It has no real eigenvalues, but it is orthogonal and has pure imaginary eigenvalues.

MAGICL> (eig (from-list '(0d0 -1d0 1d0 0d0) '(2 2)))
(0.0d0 0.0d0)
#<MATRIX/DOUBLE-FLOAT (2x2):
   0.707     0.000
   0.000    -0.707>

Compare with numpy:

In [7]: np.linalg.eig(np.array([[0, 1.0], [-1.0, 0.0]]))
Out[7]:
(array([0.+1.j, 0.-1.j]),
 array([[0.70710678+0.j        , 0.70710678-0.j        ],
        [0.        +0.70710678j, 0.        -0.70710678j]]))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to work when the type is complex.

MAGICL> (eig (from-list '(0d0 -1d0 1d0 0d0) '(2 2) :type '(complex double-float)))
(#C(0.0d0 0.9999999999999997d0) #C(2.7755575615628914d-17 -1.0d0))
#<MATRIX/COMPLEX-DOUBLE-FLOAT (2x2):
   0.707 + 0.000j     0.707 + 0.000j
  -0.000 - 0.707j    -0.000 + 0.707j>

I think this brings up the question of if eig on a real tensor should be allowed to return complex results. I am fine with it doing that and it shouldn't be too hard to implement.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think eig should be allowed to return complex results. FWIW it's not a bad idea to have a variant for real symmetric (or complex hermitian) matrices -- this is an approach taken by numpy (e.g. linalg.eigh) as well as underlying lapack routines.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Also, for the purposes of getting this PR over the finish line, I don't view adding any more top-level routines as a requirement, but it's worth keeping in mind for the future.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW there is a hermitian-eig defined. It should be added to the documentation

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes eig fails (gives erroneous results) when applied to Hermitian matrices whereas hermitian-eig succeeds. This is not a problem with our software, though. In an ideal world, MAGICL would know that the matrix is symmetric and it would call the appropriate routine. This is not something that belongs to the current PR, though.

| `(qr a)` (Returns `(VALUES Q R)`) | `[Q,R,P]=qr(a,0)` | `Q,R = scipy.linalg.qr(a)` | QR factorization of matrix |
| `(ql a)` (Returns `(VALUES Q L)`) | | | QL factorization of matrix |
| `(rq a)` (Returns `(VALUES R Q)`) | | `R,Q = scipy.linalg.rq(a)` | RQ factorization of matrix |
| `(lq a)` (Returns `(VALUES L Q)`) | | | LQ factorization of matrix |
| `(lu a)` (Returns `(VALUES LU IPIV)`) | `[L,U,P]=lu(a)` | `L,U = scipy.linalg.lu(a)` or `LU,P=scipy.linalg.lu_factor(a)` | LU decomposition of matrix |
| `(csd a)` (Returns `(VALUES U SIGMA VT)`) | | | Cosine-sine decomposition of matrix |
Loading