├── .gitignore ├── .travis.yml ├── CHANGES.txt ├── LICENSE ├── META6.json ├── README.md ├── appveyor.yml ├── dist.ini ├── lib └── Math │ ├── Matrix.pm6 │ ├── Matrix.pod │ └── Matrix │ ├── ArrayOfArray.pm6 │ ├── Type.pm6 │ └── Util.pm6 ├── roadmap.txt ├── t ├── 010-load.t ├── 011-meta.t ├── 020-constructors.t ├── 021-accessors.t ├── 022-converter.t ├── 030-property-bool.t ├── 031-property-num.t ├── 040-derived.t ├── 041-decomposition.t ├── 050-math-operation.t ├── 051-list-like-operation.t └── 052-structural-operation.t └── update_readme.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | /lib/.precomp/* 3 | .precomp/* 4 | /t/.precomp/* 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl6 2 | perl6: 3 | - latest 4 | install: 5 | - rakudobrew build zef 6 | - zef install --deps-only . 7 | - zef install Test::META 8 | script: 9 | - PERL6_TEST_META=1 PERL6LIB=$PWD/lib prove -e perl6 -r t/ 10 | sudo: false 11 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.4.0: 2 | § 3 | * merging crout algorithm into LU decomposition method 4 | * cholesky decomposition knows now also LDL format (optionally :diagonal) 5 | ! change method names to all decompositions to meet the perl 6 standards 6 | 7 | 8 | 0.3.9: 9 | § 2019.2.13 10 | + bool property is-tridiagonal, is-anti-diagonal 11 | + method .H to created conjugated transposed matrix 12 | * bool property is-triangular got 5 attributes (optionally :upper, :lower, :strict, :unit, :atomic) 13 | * added multiply_vector 14 | x do not allow undefined values in elements 15 | x add and multiply_scalar to column was broken 16 | 17 | 18 | 0.3.8: 19 | § 2018.11.26 20 | ! reworked all map methods 21 | ! renamed cell to element 22 | * map, map-with-index enhanced 23 | - map-row, map-column, map-index 24 | + accessor skew-diagonal 25 | + bool property catalecticant 26 | + numeric property bandwith, lower-bandwith, upper-bandwith 27 | x MM list of lists works now 28 | = streamlined TOC 29 | = reordered some chapter 30 | = rewrote some docs, more links 31 | 32 | 0.3.7: 33 | § 2018.11.15 34 | + added bool property is-diagonal-constant 35 | * get more than main diagonal (emulated mathematica API) 36 | * question for strict diagonal matrix (upper and lower) 37 | ! condense API of add and multiply 38 | ! remove subtract 39 | = streamlined TOC 40 | 41 | 0.3.6: 42 | § 2018.10.18 43 | + method map-index and map-with-index 44 | - method map-cell removed 45 | ! renamed dotProduct -> dot-product and tensorProduct -> tensor-product 46 | ! renamed kernel -> nullity 47 | ! constructors reject now empty matrices 48 | ! 2 unconventional submatrix variants receive now optional named arguments and can be combined 49 | 50 | 0.3.5: 51 | § 2018.9.23 52 | * accept list of list as input for new 53 | + added method minor, that computes well a Minor 54 | + added method adjugated, computes the derived matrix called adjugate (classical adjoint) 55 | + added method elem that returns true if all matrix values are within a set 56 | + added Range converter method 57 | ! prefix + aka numeric now returns Euclidean norm 58 | ! submatrix(Int, Int, Int, Int) is now submatix (Range, Range) and accepts also * 59 | = improving docs of derived matrices 60 | 61 | 0.3.0: 62 | § 2018.9.13 63 | * redone .gist allows now complex numbers and scientific notation 64 | + added method .AT-POS for $matrix[1][2] syntax 65 | + added converter to hash context 66 | * cell-type can now be Bool and FatRat 67 | * cell-type changed into narrowest-cell-type and widest-cell-type 68 | * making width and height of the table gist display optional arguments 69 | - fixed prefix % and @ ops 70 | = crosslinking doc headers 71 | 72 | 0.2.5: 73 | § 2018.8.20 74 | + added MM operator as shortcut for Math::Matrix.new() 75 | + added constructor that takes a string 76 | ! .Str outputs now same format that constructor takes 77 | - removed .full 78 | + added splice-rows and splice-columns replacing prepend-*, append-* and planned insert-* and delete-* 79 | + added own methods for .list (flat) and .Array context 80 | + added proper .gist for type object (like all types have) showing (Math::Matrix) 81 | + new property cell-type return widest cell type 82 | = refreshed doc synopsis and description, improved docs overall 83 | & added .appveyor.yml file: win CI support to github project 84 | 85 | 0.2.0: 86 | § 2018.3.26 87 | + add methods: add-row, add-column, multiply-row, multiply-column, map-row, map-column 88 | + add methods move-row, move-column, swap-rows, swap-columns, 89 | prepend-vertically, append-vertically, prepend-horizontally, append-horizontally 90 | + add method elem to check if any cell is equal to value or in a range 91 | + new property: antisymmetric 92 | = new category: list like ops 93 | x submatrix with 2 Int parameter works now as expected 94 | x accessor column becomes faster 95 | = move docs into Matrix.pod 96 | = added docs 97 | 98 | 0.1.8: 99 | § 2018.3.20 100 | + added method: tensorProduct, alias infix op x, unicode alias ⊗ 101 | + added method: conjugated, is-self-adjoint, is-unitary, is-positive-semidefinite 102 | - more property setting optimization in constructors 103 | - new category for methods: structural ops 104 | 105 | 0.1.7: 106 | - 2018.3.18 107 | - added methods: elems, list-rows and list-columns, full 108 | - documented, but forgot to implement: commutative + and - op 109 | - limit gist for optimal shell output 110 | - change norm names rowsum => row-sum, columnsum => column-sum 111 | - added docs, operator got own section 112 | - started changelog 113 | 114 | 0.1.6: 115 | § 2018.3.15 116 | + added methods: reduce-rows and reduce-columns, 117 | = added and clarified docs 118 | 119 | 0.1.5: 120 | § 2018.3.14 121 | ! accessors column and row now return consistently lists (like diagonal) 122 | * expanded power of submatrix accessor 123 | + added ops: |.| as alias for determinant, and prefix - as alias for negated 124 | ! renamed apply method to map 125 | ! renamed negate method to negated (consistency, transposed and inverted) 126 | = added and clarified docs 127 | 128 | Legend: 129 | § release info 130 | + new (added) feature 131 | - removed feature 132 | * enhanced feature 133 | / reduced feature 134 | ! API change 135 | x bug fiX 136 | = documentation 137 | & project infrastructure 138 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Artistic License 2.0 2 | 3 | Copyright (c) 2000-2006, The Perl Foundation. 4 | 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | This license establishes the terms under which a given free software 11 | Package may be copied, modified, distributed, and/or redistributed. 12 | The intent is that the Copyright Holder maintains some artistic 13 | control over the development of that Package while still keeping the 14 | Package available as open source and free software. 15 | 16 | You are always permitted to make arrangements wholly outside of this 17 | license directly with the Copyright Holder of a given Package. If the 18 | terms of this license do not permit the full use that you propose to 19 | make of the Package, you should contact the Copyright Holder and seek 20 | a different licensing arrangement. 21 | 22 | Definitions 23 | 24 | "Copyright Holder" means the individual(s) or organization(s) 25 | named in the copyright notice for the entire Package. 26 | 27 | "Contributor" means any party that has contributed code or other 28 | material to the Package, in accordance with the Copyright Holder's 29 | procedures. 30 | 31 | "You" and "your" means any person who would like to copy, 32 | distribute, or modify the Package. 33 | 34 | "Package" means the collection of files distributed by the 35 | Copyright Holder, and derivatives of that collection and/or of 36 | those files. A given Package may consist of either the Standard 37 | Version, or a Modified Version. 38 | 39 | "Distribute" means providing a copy of the Package or making it 40 | accessible to anyone else, or in the case of a company or 41 | organization, to others outside of your company or organization. 42 | 43 | "Distributor Fee" means any fee that you charge for Distributing 44 | this Package or providing support for this Package to another 45 | party. It does not mean licensing fees. 46 | 47 | "Standard Version" refers to the Package if it has not been 48 | modified, or has been modified only in ways explicitly requested 49 | by the Copyright Holder. 50 | 51 | "Modified Version" means the Package, if it has been changed, and 52 | such changes were not explicitly requested by the Copyright 53 | Holder. 54 | 55 | "Original License" means this Artistic License as Distributed with 56 | the Standard Version of the Package, in its current version or as 57 | it may be modified by The Perl Foundation in the future. 58 | 59 | "Source" form means the source code, documentation source, and 60 | configuration files for the Package. 61 | 62 | "Compiled" form means the compiled bytecode, object code, binary, 63 | or any other form resulting from mechanical transformation or 64 | translation of the Source form. 65 | 66 | 67 | Permission for Use and Modification Without Distribution 68 | 69 | (1) You are permitted to use the Standard Version and create and use 70 | Modified Versions for any purpose without restriction, provided that 71 | you do not Distribute the Modified Version. 72 | 73 | 74 | Permissions for Redistribution of the Standard Version 75 | 76 | (2) You may Distribute verbatim copies of the Source form of the 77 | Standard Version of this Package in any medium without restriction, 78 | either gratis or for a Distributor Fee, provided that you duplicate 79 | all of the original copyright notices and associated disclaimers. At 80 | your discretion, such verbatim copies may or may not include a 81 | Compiled form of the Package. 82 | 83 | (3) You may apply any bug fixes, portability changes, and other 84 | modifications made available from the Copyright Holder. The resulting 85 | Package will still be considered the Standard Version, and as such 86 | will be subject to the Original License. 87 | 88 | 89 | Distribution of Modified Versions of the Package as Source 90 | 91 | (4) You may Distribute your Modified Version as Source (either gratis 92 | or for a Distributor Fee, and with or without a Compiled form of the 93 | Modified Version) provided that you clearly document how it differs 94 | from the Standard Version, including, but not limited to, documenting 95 | any non-standard features, executables, or modules, and provided that 96 | you do at least ONE of the following: 97 | 98 | (a) make the Modified Version available to the Copyright Holder 99 | of the Standard Version, under the Original License, so that the 100 | Copyright Holder may include your modifications in the Standard 101 | Version. 102 | 103 | (b) ensure that installation of your Modified Version does not 104 | prevent the user installing or running the Standard Version. In 105 | addition, the Modified Version must bear a name that is different 106 | from the name of the Standard Version. 107 | 108 | (c) allow anyone who receives a copy of the Modified Version to 109 | make the Source form of the Modified Version available to others 110 | under 111 | 112 | (i) the Original License or 113 | 114 | (ii) a license that permits the licensee to freely copy, 115 | modify and redistribute the Modified Version using the same 116 | licensing terms that apply to the copy that the licensee 117 | received, and requires that the Source form of the Modified 118 | Version, and of any works derived from it, be made freely 119 | available in that license fees are prohibited but Distributor 120 | Fees are allowed. 121 | 122 | 123 | Distribution of Compiled Forms of the Standard Version 124 | or Modified Versions without the Source 125 | 126 | (5) You may Distribute Compiled forms of the Standard Version without 127 | the Source, provided that you include complete instructions on how to 128 | get the Source of the Standard Version. Such instructions must be 129 | valid at the time of your distribution. If these instructions, at any 130 | time while you are carrying out such distribution, become invalid, you 131 | must provide new instructions on demand or cease further distribution. 132 | If you provide valid instructions or cease distribution within thirty 133 | days after you become aware that the instructions are invalid, then 134 | you do not forfeit any of your rights under this license. 135 | 136 | (6) You may Distribute a Modified Version in Compiled form without 137 | the Source, provided that you comply with Section 4 with respect to 138 | the Source of the Modified Version. 139 | 140 | 141 | Aggregating or Linking the Package 142 | 143 | (7) You may aggregate the Package (either the Standard Version or 144 | Modified Version) with other packages and Distribute the resulting 145 | aggregation provided that you do not charge a licensing fee for the 146 | Package. Distributor Fees are permitted, and licensing fees for other 147 | components in the aggregation are permitted. The terms of this license 148 | apply to the use and Distribution of the Standard or Modified Versions 149 | as included in the aggregation. 150 | 151 | (8) You are permitted to link Modified and Standard Versions with 152 | other works, to embed the Package in a larger work of your own, or to 153 | build stand-alone binary or bytecode versions of applications that 154 | include the Package, and Distribute the result without restriction, 155 | provided the result does not expose a direct interface to the Package. 156 | 157 | 158 | Items That are Not Considered Part of a Modified Version 159 | 160 | (9) Works (including, but not limited to, modules and scripts) that 161 | merely extend or make use of the Package, do not, by themselves, cause 162 | the Package to be a Modified Version. In addition, such works are not 163 | considered parts of the Package itself, and are not subject to the 164 | terms of this license. 165 | 166 | 167 | General Provisions 168 | 169 | (10) Any use, modification, and distribution of the Standard or 170 | Modified Versions is governed by this Artistic License. By using, 171 | modifying or distributing the Package, you accept this license. Do not 172 | use, modify, or distribute the Package, if you do not accept this 173 | license. 174 | 175 | (11) If your Modified Version has been derived from a Modified 176 | Version made by someone other than you, you are nevertheless required 177 | to ensure that your Modified Version complies with the requirements of 178 | this license. 179 | 180 | (12) This license does not grant you the right to use any trademark, 181 | service mark, tradename, or logo of the Copyright Holder. 182 | 183 | (13) This license includes the non-exclusive, worldwide, 184 | free-of-charge patent license to make, have made, use, offer to sell, 185 | sell, import and otherwise transfer the Package with respect to any 186 | patent claims licensable by the Copyright Holder that are necessarily 187 | infringed by the Package. If you institute patent litigation 188 | (including a cross-claim or counterclaim) against any party alleging 189 | that the Package constitutes direct or contributory patent 190 | infringement, then this Artistic License to you shall terminate on the 191 | date that such litigation is filed. 192 | 193 | (14) Disclaimer of Warranty: 194 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 195 | IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 196 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 197 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 198 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 199 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 200 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 201 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 202 | -------------------------------------------------------------------------------- /META6.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth" : "github:pierre-vigier", 3 | "authors" : [ 4 | "github:pierre-vigier", 5 | "github:lichtkind" 6 | ], 7 | "build-depends" : [ ], 8 | "depends" : [ "AttrX::Lazy" ], 9 | "description" : "create, compare, compute and measure matrices", 10 | "license" : "Artistic-2.0", 11 | "name" : "Math::Matrix", 12 | "perl" : "6.c", 13 | "provides" : { 14 | "Math::Matrix" : "lib/Math/Matrix.pm6", 15 | "Math::Matrix::Util" : "lib/Math/Matrix/Util.pm6", 16 | "Math::Matrix::Type" : "lib/Math/Matrix/Type.pm6", 17 | "Math::Matrix::ArrayOfArray" : "lib/Math/Matrix/ArrayOfArray.pm6" 18 | }, 19 | "resources" : [ ], 20 | "source-url" : "git://github.com/pierre-vigier/Perl6-Math-Matrix.git", 21 | "tags" : [ "Math" ], 22 | "test-depends" : [ "Test", "Test::META"], 23 | "version" : "0.3.9" 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/pierre-vigier/Perl6-Math-Matrix.svg?branch=master)](https://travis-ci.org/pierre-vigier/Perl6-Math-Matrix) 2 | NAME 3 | ==== 4 | 5 | Math::Matrix - create, compare, compute and measure 2D matrices 6 | 7 | VERSION 8 | ======= 9 | 10 | 0.3.9 11 | 12 | !MOVING! 13 | ======== 14 | 15 | This repo will be moved to a [different place](https://github.com/lichtkind/Raku-Math-Matrix) soon. 16 | 17 | SYNOPSIS 18 | ======== 19 | 20 | Matrices are tables with rows and columns (index counting from 0) of numbers (Numeric type - Bool or Int or Num or Rat or FatRat or Complex): 21 | 22 | transpose, invert, negate, add, multiply, dot product, tensor product, 22 ops, determinant, rank, norm 23 | 14 numerical properties, 26 boolean properties, 3 decompositions, submatrix, splice, map, reduce and more 24 | 25 | Table of Content: 26 | 27 | * [Methods](#methods) 28 | 29 | * [Operators](#operators) 30 | 31 | * [Export Tags](#export-tags) 32 | 33 | * [Authors](#authors) 34 | 35 | * [License](#license) 36 | 37 | DESCRIPTION 38 | =========== 39 | 40 | Because the list based, functional toolbox of Raku is not enough to calculate matrices comfortably, there is a need for a dedicated data type. The aim is to provide a full featured set of structural and mathematical operations that integrate fully with the Raku conventions. This module is pure perl and we plan to use native shaped arrays one day. 41 | 42 | Matrices are readonly - methods and operators do create new matrix objects. All methods return readonly data or deep clones - also the constructor does a deep clone of provided data. In that sense the library is thread safe. 43 | 44 | All computation heavy properties will be calculated lazily and cached. Mathematically or otherwise undefined operations will cause an exception. 45 | 46 | [METHODS](#synopsis) 47 | ==================== 48 | 49 | * **[constructors](#constructors)**: [new []](#new--), [new ()](#new---1), [new ""](#new---2), [new-zero](#new-zero), [new-identity](#new-identity), [new-diagonal](#new-diagonal), [new-vector-product](#new-vector-product) 50 | 51 | * **[accessors](#accessors)**: [element](#element), [AT-POS](#at-pos), [row](#row), [column](#column), [diagonal](#diagonal), [skew-diagonal](#skew-diagonal), [submatrix](#submatrix) 52 | 53 | * **[converter](#converter)**: [Bool](#bool), [Str](#str), [Numeric](#numeric), [Range](#range), [Array](#array), [list](#list), [list-rows](#list-rows), [list-columns](#list-columns), [Hash](#hash), [gist](#gist), [perl](#perl) 54 | 55 | * **[boolean properties](#boolean-properties)**: [zero](#is-zero), [identity](#is-identity), [square](#is-square), [triangular](#is-triangular), [tri-](#is-tridiagonal), [anti-](#is-anti-diagonal), [diagonal](#is-diagonal), [-dominant](#is-diagonally-dominant), [-constant](#is-diagonal-constant), [catalecticant](#is-catalecticant), [anti-](#is-antisymmetric), [symmetric](#is-symmetric), [unitary](#is-unitary), [self-adjoint](#is-self-adjoint), [invertible](#is-invertible), [orthogonal](#is-orthogonal), [positive-definite](#is-positive-definite), [positive-semidefinite](#is-positive-semidefinite) 56 | 57 | * **[numeric properties](#numeric-properties)**: [size](#size), [density](#density), [bandwith](#bandwith), [trace](#trace), [rank](#rank), [nullity](#nullity), [determinant](#determinant), [minor](#minor), [norm](#norm), [condition](#condition), [element-type](#element-type) 58 | 59 | * **[derived matrices](#drived-matrices)**: [transposed](#transposed), [negated](#negated), [conjugated](#conjugated), [adjugated](#adjugated), [inverted](#inverted), [reduced-row-echelon-form](reduced-row-echelon-form) 60 | 61 | * **[decompositions](#decompositions)**: [LU](#LU-decomposition), [LDU](#LU-decomposition), [Cholesky](#Cholesky-decomposition), [LDL](#Cholesky-decomposition) 62 | 63 | * **[math ops](#mathematical-operations)**: [equal](#equal), [add](#add), [multiply](#multiply), [dot-product](#dot-product), [tensor-product](#tensor-product) 64 | 65 | * **[list like ops](#list-like-operations)**: [elems](#elems), [elem](#elem), [cont](#cont), [map](#map), [map-with-index](#map-with-index), [reduce](#reduce), [reduce-rows](#reduce-rows), [reduce-columns](#reduce-columns) 66 | 67 | * **[structural ops](#structural-operations)**: [move-row](#move-row), [move-column](#move-column), [swap-rows](#swap-rows), [swap-columns](#swap-columns), [splice-rows](#splice-rows), [splice-columns](#splice-columns) 68 | 69 | * **[shortcuts](#shortcuts)**: [T](#transposed), [conj](#conjugated), [det](#determinant), [rref](#reduced-row-echelon-form) 70 | 71 | * **[operators](#operator-methods)**: MM, ?, ~, |, @, %, +, -, *, **, dot, ⋅, ÷, X*, ⊗, ==, ~~, ❘ ❘, ‖ ‖, [ ] 72 | 73 | [Constructors](#methods) 74 | ------------------------ 75 | 76 | Methods that create a new Math::Matrix object. The default is of course .new, which can take array of array of values (fastest) or one string. Additional constructers: [new-zero](#new-zero), [new-identity](#new-identity), [new-diagonal](#new-diagonal) and [new-vector-product](#new-vector-product) are there for convenience and to optimize property calculation. 77 | 78 | ### [new( [[...],...,[...]] )](#constructors) 79 | 80 | The default constructor, takes arrays of arrays of numbers as the only required parameter. Each second level array represents a row in the matrix. That is why their length has to be the same. Empty rows or columns we not be accepted. 81 | 82 | say Math::Matrix.new( [[1,2],[3,4]] ) : 83 | 84 | 1 2 85 | 3 4 86 | 87 | Math::Matrix.new([<1 2>,<3 4>]); # does the same, WARNING: doesn't work with complex numbers 88 | Math::Matrix.new( [[1]] ); # one element 1*1 matrix (exception where you don't have to mind comma) 89 | Math::Matrix.new( [[1,2,3],] ); # one row 1*3 matrix, mind the trailing comma 90 | Math::Matrix.new( [$[1,2,3]] ); # does the same, if you don't like trailing comma 91 | Math::Matrix.new( [[1],[2]] ); # one column 2*1 matrix 92 | 93 | use Math::Matrix :MM; # tag :ALL works too 94 | MM [[1,2],[3,4]]; # shortcut 95 | 96 | ### [new( ((...),...,(...)) )](#constructors) 97 | 98 | Instead of square brackets you can use round ones too and use a list of lists as argument too. 99 | 100 | say Math::Matrix.new( ((1,2),(3,4)) ); 101 | say MM ((1,2),(3,4)) : 102 | 103 | 1 2 104 | 3 4 105 | 106 | ### [new( "..." )](#constructors) 107 | 108 | Alternatively you can define the matrix from a string, which makes most sense while using heredocs. 109 | 110 | Math::Matrix.new("1 2 \n 3 4"); # our demo matrix as string 111 | Math::Matrix.new: q:to/END/; # indent as you wish 112 | 1 2 113 | 3 4 114 | END 115 | 116 | use Math::Matrix :ALL; # 117 | MM '1'; # 1 * 1 matrix, this case begs for a shortcut 118 | 119 | ### [new-zero](#constructors) 120 | 121 | This method is a constructor, that returns a *zero* matrix, which is sometimes called empty. That is a matrix, which holds zeros in all its [element](#element)s (as checked by [is-zero](#is-zero)). 122 | 123 | *new-zero* needs one ore two integer arguments. These are the numbers of rows and columns - the size [size](#size) of the new matrix. If both numbers are the same, you can omit the ladder and get a [quadratic](#is-square) matrix filld with zeros. 124 | 125 | say Math::Matrix.new-zero( 3, 4 ) : 126 | 127 | 0 0 0 0 128 | 0 0 0 0 129 | 0 0 0 0 130 | 131 | say Math::Matrix.new-zero( 2 ) : 132 | 133 | 0 0 134 | 0 0 135 | 136 | ### [new-identity](#constructors) 137 | 138 | This method creates a new *identity matrix* (as checked by [is-identity](#is-identity)). It contains only zeros, except in the main [diagonal](#diagonal), which consist of ones. Since identity matrices have to be [quadratic](#is-square), *new-identity* requires only one integer arguments, which sets the number of rows and columns of the new matrix. 139 | 140 | say Math::Matrix.new-identity( 3 ) : 141 | 142 | 1 0 0 143 | 0 1 0 144 | 0 0 1 145 | 146 | ### [new-diagonal](#constructors) 147 | 148 | creates a *diagonal* matrix (as checked by [is-diagonal](#is-diagonal)), which contains zeros, except in the main [diagonal](#diagonal), that can hold arbitrary values. The only required argument is a list these arbitrary numbers, which will become the content of the main diagonal. 149 | 150 | say Math::Matrix.new-diagonal( 2, 4, 5 ) : 151 | 152 | 2 0 0 153 | 0 4 0 154 | 0 0 5 155 | 156 | ### [new-vector-product](#constructors) 157 | 158 | creates a matrix by calculating the [tensor-product](#tensor-product) of two vectors, which are in a list or Array form the two required arguments of the method. Both lists don't have to be of the same size. The length of the first list will determine the amount of rows and the length of the second the amount of columns. 159 | 160 | say Math::Matrix.new-vector-product([1,2,3],[2,3,4]) : 161 | 162 | * 2 3 4 163 | 1 1*2 1*3 1*4 2 3 4 164 | 2 2*2 2*3 2*4 = 4 6 8 165 | 3 3*2 3*3 3*4 6 9 12 166 | 167 | [Accessors](#methods) 168 | --------------------- 169 | 170 | Methods that return the content of selected elements. 171 | 172 | [element](#element), [AT-POS](#at-pos), [row](#row), [column](#column), [diagonal](#diagonal), [skew-diagonal](skew-diagonal), **[submatrix](submatrix)**: ([leaving out one](#leaving-out-one), [leaving out more](#leaving-out-more), [reordering](#reordering)) 173 | 174 | ### [element](#accessors) 175 | 176 | Gets value of one element in [row](#row) (first parameter) and [column](#column) (second parameter - counting always from 0). Sometimes its called matrix cell, to distinct from other type of elements. See: [elems](elems), [elem](elem), [element-type](#element-type) 177 | 178 | my $matrix = Math::Matrix.new([[1,2],[3,4]]); 179 | say $matrix.element(0,1) : 2 180 | say $matrix[0][1] # array syntax alias 181 | 182 | ### [AT-POS](#accessors) 183 | 184 | Gets row as array to enable direct postcircumfix syntax as shown in last example. 185 | 186 | say $matrix.AT-POS(0) : [1,2] 187 | say $matrix[0] # operator alias 188 | say $matrix.Array[0] # long alias with converter method Array 189 | 190 | ### [row](#accessors) 191 | 192 | returns the values of specified row (first required parameter) as a list. 193 | 194 | say Math::Matrix.new([[1,2],[3,4]]).row(0) : (1, 2) 195 | 196 | ### [column](#accessors) 197 | 198 | returns values of specified column (first required parameter) as a list. 199 | 200 | say Math::Matrix.new([[1,2],[3,4]]).column(0) : (1, 3) 201 | 202 | ### [diagonal](#accessors) 203 | 204 | Called without an argument, it returns the values of main diagonal as a list. The main diagonal starts at the left upper corner (index [0][0]). Every next cell of the diagonal is reached by going one step below and one step to the right (indices [x][x] - x being an integer within the size of the matrix). 205 | 206 | Use the optional parameter of the method to get any other parallel diagonal. A positive value for a parallel diagonale above and to the right of the main one - a negative value to access a parallel diagonal to the left and below the main diagonal. 0 is the default value. The matrix does not have to be a quadratic ([square](#is-square)). 207 | 208 | say Math::Matrix.new([[1,2],[3,4]] ).diagonal : (1, 4) 209 | say Math::Matrix.new([[1,2],[3,4]] ).diagonal(1) : (2) 210 | say Math::Matrix.new([[1,2],[3,4],[5,6]]).diagonal(-1): (3, 6) 211 | 212 | ### [skew-diagonal](#accessors) 213 | 214 | Unlike a *diagonal*, which runs from the left upper corner to the right lower, the (main) skew diagonal is only defined for [square](#is-square) matrixes and runs from left lower corner to the right upper (from $matrix[m][0] to $matrix[0][m]). Use the optional argument to get any other parallel skew diagonal. Positive value for the ones below - negative above. 215 | 216 | say $matrix.skew-diagonal : (2, 3) 217 | say $matrix.skew-diagonal(0) : (2, 3) 218 | say $matrix.skew-diagonal(-1) : (1) 219 | say $matrix.skew-diagonal(1): (4) 220 | 221 | ### [submatrix](#accessors) 222 | 223 | Returns a matrix that might miss certain [row](#row)s and columns of the original. This method accepts arguments in three different formats. The first follows the strict mathematical definition of a submatrix, the second supports a rather visual understanding of the term and the third is a way to get almost any combination rows and columns you might wish for. To properly present these functions, we base the next examples upon this matrix: 224 | 225 | say $m: 1 2 3 4 226 | 2 3 4 5 227 | 3 4 5 6 228 | 4 5 6 7 229 | 230 | #### [leaving out one](#submatrix) 231 | 232 | In mathematics, a submatrix is built by leaving out one [row](#row) and one [column](#column). In the two positional argument format you name these by their index ($row, $column). 233 | 234 | say $m.submatrix(1,2) : 1 2 4 235 | 3 4 6 236 | 4 5 7 237 | 238 | #### [leaving out more](#submatrix) 239 | 240 | If you provide two ranges (row-min .. row-max, col-min .. col-max - both optional) to the appropriately named arguments, you get the excerpt of the matrix, that contains only the requested rows and columns - in the original order. 241 | 242 | say $m.submatrix( rows => 1..1, columns => 1..2) : 3 4 say $m.submatrix( rows => 1..1, columns => 2..*) : 4 5 say $m.submatrix( rows => 1..2 ) : 2 3 4 5 3 4 5 6 243 | 244 | #### [reordering](#submatrix) 245 | 246 | Alternatively each (as previously) named argument can also take a list (or array) of values, as created my the sequence operator (...). The result will be a matrix with that selection of rows and columns. Please note, you may pick rows/columns in any order and as many times you prefer. 247 | 248 | $m.submatrix(rows => (1,2), columns => (3,2)): 5 4 249 | 6 5 250 | 251 | $m.submatrix(rows => (1...2), columns => (3,2)) # same thing 252 | 253 | Arguments with ranges and lists can be mixed and are in both cases optional. If you provide none of them, the result will be the original matrix. 254 | 255 | say $m.submatrix( rows => (1,) ) : 3 4 5 256 | 257 | $m.submatrix(rows => (1..*), columns => (3,2)): 5 4 258 | 6 5 259 | 260 | Even more powerful or explicit in syntax are the [structural ops](#structural-operations). 261 | 262 | [Converter](#methods) 263 | --------------------- 264 | 265 | Methods that convert a matrix into other types: [Bool](#bool), [Str](#str), [Numeric](#numeric), [Range](#range), [Array](#array), [Hash](#hash), [list](#list), [list-rows](#list-rows), [list-columns](#list-columns) or allow different views on the overall content (output formats): [gist](#gist), [perl](#perl). 266 | 267 | ### [Bool](#converter) 268 | 269 | Conversion into Bool context. Returns False if matrix is zero (all elements equal zero as in [is-zero](#is-zero)), otherwise True. 270 | 271 | $matrix.Bool 272 | ? $matrix # alias op 273 | if $matrix # matrix in Bool context too 274 | 275 | ### [Str](#converter) 276 | 277 | Returns values of all [element](#element)s, separated by one whitespace, rows by new line. This is the same format as expected by [new("")](#new---2). Str is called implicitly by put and print. A shortened version is provided by [gist](#gist) 278 | 279 | say Math::Matrix.new([[1,2],[3,4]]).Str: 280 | 281 | 1 2 # meaning: "1 2\n3 4" 282 | 3 4 283 | 284 | ~$matrix # alias op 285 | 286 | ### [Numeric](#converter) 287 | 288 | Conversion into Numeric context. Returns Euclidean [norm](#norm). Please note, only a prefix operator + (as in: + $matrix) will call this Method. An infix (as in $matrix + $number) calls $matrix.add($number). 289 | 290 | $matrix.Numeric 291 | + $matrix # alias op 292 | 293 | ### [Range](#converter) 294 | 295 | Returns an range object that reflects the content of all [element](#element)s. Please note that complex number can not be endpoints of ranges. 296 | 297 | say $matrix.Range: 1..4 298 | 299 | To get single endpoints you could write: 300 | 301 | say $matrix.Range.min: 1 302 | say $matrix.list.max: 4 303 | 304 | ### [Array](#converter) 305 | 306 | Content of all [element](#element)s as an array of arrays (same format that was put into [new([...])](#new--)). 307 | 308 | say Math::Matrix.new([[1,2],[3,4]]).Array : [[1 2] [3 4]] 309 | say @ $matrix # alias op, space between @ and $ needed 310 | 311 | ### [list](#converter) 312 | 313 | Returns a flat list with all [element](#element)s (same as .list-rows.flat.list). 314 | 315 | say $matrix.list : (1 2 3 4) 316 | say |$matrix # alias op 317 | 318 | ### [list-rows](#converter) 319 | 320 | Returns a list of lists, reflecting the row-wise content of the matrix. Same format as [new ()](#new---1) takes in. 321 | 322 | say Math::Matrix.new( [[1,2],[3,4]] ).list-rows : ((1 2) (3 4)) 323 | say Math::Matrix.new( [[1,2],[3,4]] ).list-rows.flat : (1 2 3 4) 324 | 325 | ### [list-columns](#converter) 326 | 327 | Returns a list of lists, reflecting the row-wise content of the matrix. 328 | 329 | say Math::Matrix.new( [[1,2],[3,4]] ).list-columns : ((1 3) (2 4)) 330 | say Math::Matrix.new( [[1,2],[3,4]] ).list-columns.flat : (1 3 2 4) 331 | 332 | ### [Hash](#converter) 333 | 334 | Gets you a nested key - value hash. 335 | 336 | say $matrix.Hash : { 0 => { 0 => 1, 1 => 2}, 1 => {0 => 3, 1 => 4} } 337 | say % $matrix # alias op, space between % and $ still needed 338 | 339 | ### [gist](#converter) 340 | 341 | Limited tabular view, optimized for shell output. Just cuts off excessive columns that do not fit into standard terminal and also stops after 20 rows. If you call it explicitly, you can add width and height (char count) as optional arguments. Might even not show all decimals. Several dots will hint that something is missing. It is implicitly called by say. For a full view use [Str](#str). 342 | 343 | say $matrix; # output of a matrix with more than 100 elements 344 | 345 | 1 2 3 4 5 .. 346 | 3 4 5 6 7 .. 347 | ... 348 | 349 | say $matrix.gist(max-chars => 100, max-rows => 5 ); 350 | 351 | max-chars is the maximum amount of characters in any [row](#row) of output (default is 80). max-rows is the maximum amount of rows gist will put out (default is 20). After gist ist called once (with or without arguments) the output will be cached. So next time you call: 352 | 353 | say $matrix # 100 x 5 is still the max 354 | 355 | You change the cache by calling gist with arguments again. 356 | 357 | ### [perl](#converter) 358 | 359 | Conversion into String that can reevaluated into the same object later using default constructor. 360 | 361 | my $clone = eval $matrix.perl; # same as: $matrix.clone 362 | 363 | [Boolean Properties](#methods) 364 | ------------------------------ 365 | 366 | These are mathematical properties, a given matrix has or not. Thus, the return value is a always of boolean type (Bool). Arguments, like in case of [triangular](#is-triangular) and [is-diagonally-dominant](#is-diagonally-dominant) are only necessary, when a method can tell you about a group of closely related properties. 367 | 368 | [zero](#is-zero), [identity](#is-identity), [square](#is-square), **[triangular](#is-triangular)**: ([upper](#upper-triangular), [lower](#lower-triangular), [strict](#strict-triangular), [unit](#unit-triangular), [atomic](#atomic-triangular)), [diagonal](#is-diagonal), [anti-diagonal](#is-anti-diagonal), [tridiagonal](#is-tridiagonal), [diagonal-constant](#is-diagonal-constant), [catalecticant](#is-catalecticant), [symmetric](#is-symmetric), [anti-symmetric](#is-antisymmetric), [self-adjoint](#is-self-adjoint), [invertible](#is-invertible), [orthogonal](#is-orthogonal), [unitary](#is-unitary), **[diagonally-dominant](#is-diagonally-dominant)**, [positive-definite](#is-positive-definite), [positive-semidefinite](#is-positive-semidefinite) 369 | 370 | ### [is-zero](#boolean-properties) 371 | 372 | True if every [element](#element) has value of 0 (as created by [new-zero](#new-zero)). [Density](#density) of zero matrices is 0. This matrix behaves like a zero element regarding to the [dot-product](#dot-product). Every matrix multiplied with a zero matrix is a zero matrix (A * 0 = 0 * A = 0). 373 | 374 | Example: 0 0 0 375 | 0 0 0 376 | 377 | ### [is-identity](#boolean-properties) 378 | 379 | True if every [element](#element) on the main [diagonal](#diagonal) (where row index equals column index) is 1 and any other element is 0. Such a matrix will be created by [new-identity](#new-identity). It is the neutral element in redgards to the [dot-product](#dot-product). Every matrix multiplied with a fitting identiy matrix results in the same matrix again (A * I = I * A = A). Every identity matrix has to be a square. 380 | 381 | Example: 1 0 0 382 | 0 1 0 383 | 0 0 1 384 | 385 | ### [is-square](#boolean-properties) 386 | 387 | True if number of rows and colums are the same (see [size](#size)). 388 | 389 | ### [is-triangular](#boolean-properties) 390 | 391 | [square](#is-square) matrix where all [element](#element)s above the main [diagonal](#diagonal) or all elements below the main diagonal are zero. The method accepts five optional, boolean arguments: [:upper](#upper-triangular), [:lower](#lower-triangula), [:strict](#strict-triangular), [:unit](#unit-triangular) and [:atomic](#atomic-triangular). Each argument can be used in a positive form (*:upper*), as a negative (*:!upper*), or omitted. Positively they demand a certain property, negatively the absence or opposite. When omitted, both states are acceptable. 392 | 393 | Please note that a triangular matrix can never be *:unit* and *:strict* or *:atomic* and *:strict* at the same time, nor *:!upper* and *:!lower*. A triangular matrix that is *:upper* and *:lower* [is-diagonal](#is-diagonal). [Identity](#is-identity) matrices are *:upper*, *:lower* and *:unit*. 394 | 395 | #### [upper triangular](#is-triangular) 396 | 397 | a.k.a *right triangular matrix*: all [element](#element)s left-below the main *diagonal* are zero. In other words: the [lower-bandwith](#lower-bandwith) has to be zero. 398 | 399 | $tri-matrix.is-triangular(:upper); # matrix in the example below would pass this test 400 | $tri-matrix.is-triangular(); # True, there is a lower or upper triangle 401 | 402 | Example: 1 2 5 403 | 0 3 8 404 | 0 0 7 405 | 406 | #### [lower triangular](#is-triangular) 407 | 408 | a.k.a *left triangular* matrix: every [element](#element) right and above the [diagonal](#diagonal) (where [row](#row) index is greater than [column](#column) index) are zero. In other words: the [upper-bandwith](#upper-bandwith) has to be zero. 409 | 410 | $tri-matrix.is-triangular(:lower); # matrix in the example below would pass this test 411 | $tri-matrix.is-triangular(); # True too 412 | $tri-matrix.is-triangular(:!upper); # True, because the is a triangle, but not an upper 413 | $tri-matrix.is-triangular(:lower,:!upper);# True still, even a bit redundant 414 | 415 | Example: 1 0 0 416 | 2 3 0 417 | 5 8 7 418 | 419 | #### [strict triangular](#is-triangular) 420 | 421 | are *triangular* matrices, that have a [diagonal](#diagonal) consisting only of values equal zero. 422 | 423 | $tri-matrix.is-triangular(:strict); # matrix in the example below would pass this test 424 | $tri-matrix.is-triangular(:!unit, :lower);# True too 425 | 426 | Example: 0 0 0 427 | 5 0 0 428 | 0 6 0 429 | 430 | #### [unit triangular](#is-triangular) 431 | 432 | a.k.a *unitriangular* matrices have a [diagonal](#diagonal) consisting only of values equal one. An [identity](#is-identity) matrix is *:unit*, *:upper* and *:lower*. 433 | 434 | $tri-matrix.is-triangular(:unit); # matrix in the example below would pass this test 435 | $tri-matrix.is-triangular(:unit, :lower); # False, because unit upper triangular 436 | 437 | Example: 1 2 5 438 | 0 1 8 439 | 0 0 1 440 | 441 | #### [atomic triangular](#boolean-properties) 442 | 443 | a.k.a *Frobenius* matrix is a unit triangular matrix with one column of 'none zero values. 444 | 445 | $tri-matrix.is-triangular(:atomic); # matrix in the example below would pass this test 446 | 447 | Example: 1 0 0 0 448 | 0 1 0 0 449 | 0 2 1 0 450 | 0 5 0 1 451 | 452 | ### [is-diagonal](#boolean-properties) 453 | 454 | [square](#is-square) matrix, where only elements on the main [diagonal](#diagonal) differ from 0 (as created by [new-diagonal](#new-diagonal)). 455 | 456 | $tri-matrix.is-diagonal(); 457 | $tri-matrix.is-triangular(:upper, :lower); # same thing 458 | $tri-matrix.lower-bandwith() == $tri-matrix.upper-bandwith() == 0; # True 459 | 460 | Example: 1 0 0 461 | 0 5 0 462 | 0 0 9 463 | 464 | ### [is-anti-diagonal](#boolean-properties) 465 | 466 | [square](#is-square) matrix, where only elements on the main [skew-diagonal](#skew-diagonal) differ from 0. 467 | 468 | $tri-matrix.is-anti-diagonal(); 469 | 470 | Example: 0 0 3 471 | 0 5 0 472 | 7 0 0 473 | 474 | ### [is-tridiagonal](#boolean-properties) 475 | 476 | [square](#is-square) matrix, where only elements on the main [diagonal](#diagonal) and their direct parallels differ from 0. 477 | 478 | $tri-matrix.is-tridiagonal(); 479 | $tri-matrix.lower-bandwith() < 2 and $tri-matrix.upper-bandwith() < 2; # True 480 | 481 | Example: 1 2 0 0 482 | 3 4 5 0 483 | 0 6 7 8 484 | 0 0 9 1 485 | 486 | ### [is-diagonal-constant](#boolean-properties) 487 | 488 | a.k.a. or *Töplitz* matrix is a matrix where every [diagonal](#diagonal) is the a collection of [element](#element)s that hold the same value. 489 | 490 | Example: 0 1 2 491 | -1 0 1 492 | -2 -1 0 493 | 494 | ### [is-catalecticant](#boolean-properties) 495 | 496 | a.k.a *Hankel* matrix is a [square](#is-square) matrix, where every [skew-diagonal](#skew-diagonal) is the a collection of [element](#element)s that hold the same value. *Catalecticant* matrices are [symmetric](#is-symmetric). 497 | 498 | Example: 0 1 2 499 | 1 2 3 500 | 2 3 4 501 | 502 | ### [is-symmetric](#boolean-properties) 503 | 504 | True if every [element](#element) with coordinates x y has same value as the element on y x. In other words: $matrix and $matrix.[transposed](#transposed) (alias T) are the same. 505 | 506 | Example: 1 2 3 507 | 2 5 4 508 | 3 4 7 509 | 510 | ### [is-antisymmetric](#boolean-properties) 511 | 512 | Means the [transposed](#transposed) and [negated](#negated) matrix are the same. 513 | 514 | Example: 0 2 3 515 | -2 0 4 516 | -3 -4 0 517 | 518 | ### [is-self-adjoint](#boolean-properties) 519 | 520 | A Hermitian or self-adjoint matrix is [equal](#equal) to its [transposed](#transposed) and complex [conjugated](#conjugated). 521 | 522 | Example: 1 2 3+i 523 | 2 5 4 524 | 3-i 4 7 525 | 526 | ### [is-invertible](#boolean-properties) 527 | 528 | , also called *nonsingular* or *nondegenerate*, is a [square](#is-square) matrix, which has a none zerot [determinant](#determinant). That means all rows or colums have to be independent vectors. Please check this property before calling $matrix.[inverted](#inverted), or you will get an exception. 529 | 530 | ### [is-orthogonal](#boolean-properties) 531 | 532 | An orthogonal matrix multiplied ([dot-product](#dot-product)) with its transposed derivative (T) is an [identity](#is-identity) matrix or in other words: [transposed](#transposed) and [inverted](#inverted) matrices are [equal](#equal). 533 | 534 | ### [is-unitary](#boolean-properties) 535 | 536 | An unitery matrix multiplied ([dot-product](#dot-product)) with its [concjugated](#conjugated) and transposed derivative (.conj.T) is an [identity](#is-identity) matrix, or said differently: the concjugate transposed matrix equals the [inverted](#inverted) matrix. 537 | 538 | ### [is-diagonally-dominant](#boolean-properties) 539 | 540 | True when [element](#element)s on the [diagonal](#diagonal) have a bigger (if strict) or at least equal (in none strict) absolute value than the sum of its row (sum of absolute values of the row except diagonal element). 541 | 542 | if $matrix.is-diagonally-dominant { 543 | $matrix.is-diagonally-dominant(:!strict) # same thing (default) 544 | $matrix.is-diagonally-dominant(:strict) # diagonal elements (DE) are stricly greater (>) 545 | $matrix.is-diagonally-dominant(:!strict, :along) # default 546 | $matrix.is-diagonally-dominant(:strict, :along) # DE > sum of rest row 547 | $matrix.is-diagonally-dominant(:!strict, :along) # DE >= sum of rest row and rest column 548 | 549 | ### [is-positive-definite](#boolean-properties) 550 | 551 | True if all main [minors](#minor) or all Eigenvalues are strictly greater zero. 552 | 553 | ### [is-positive-semidefinite](#boolean-properties) 554 | 555 | True if all main [minors](#minor) or all Eigenvalues are greater equal zero. 556 | 557 | [Numeric Properties](#methods) 558 | ------------------------------ 559 | 560 | Matrix properties that are expressed with a single number, which will be calculated without further input. 561 | 562 | [size](#size), [density](#density), **[bandwith](#bandwith)**: ([lower-](#lower-bandwith), [upper-](#upper-bandwith)), [trace](#trace), [rank](#rank), [nullity](#nullity), [determinant](#determinant), [minor](#minor), [norm](#norm), [condition](#condition), **[element-type](#element-type)**: ([narrowest-](#narrowest-element-type), [widest-](#widest-element-type)) 563 | 564 | ### [size](#numeric-properties) 565 | 566 | List of two values: number of rows and number of columns. 567 | 568 | say $matrix.size(); 569 | my $dim = min $matrix.size; 570 | 571 | ### [density](#numeric-properties) 572 | 573 | *Density* is the percentage of [element](#element)s which are not zero. *sparsity* = 1 - *density*. 574 | 575 | my $d = $matrix.density; 576 | 577 | ### [bandwith](#numeric-properties) 578 | 579 | Is roughly the greatest distance of a none zero value from the main [diagonal](#diagonal). A matrix that [is-diagonal](#is-diagonal) has a bandwith of 0. If there is a none zero value on an diagonal above or below the main diagonal, tha bandwith would be one. 580 | 581 | my $bw = $matrix.bandwith; 582 | 583 | #### [lower-bandwith](#bandwith) 584 | 585 | In a matrix with the lower bandwith of k, every [element](#element) with row index m and column index n, for which holds m - k > n, the content has to be zero. Literally speaking: there are k [diagonal](#diagonal)s below the main diagonal, that contain none zero values. 586 | 587 | #### [upper-bandwith](#bandwith) 588 | 589 | Analogously, every [element](#element) with m + k < n is zero if matrix has an upper bandwith of k. 590 | 591 | Example: 1 0 0 592 | 4 2 0 593 | 0 5 3 594 | 595 | The *bidiagonal* example matrix has an upper bandwith of zero and lower bandwith of one, so the overall bandwith is one. 596 | 597 | ### [trace](#numeric-properties) 598 | 599 | The trace of a [square](#is-square) matrix is the sum of the [element](#element)s on the main diagonal. In other words: sum of elements which row and column value is identical. 600 | 601 | my $tr = $matrix.trace; 602 | 603 | ### [rank](#numeric-properties) 604 | 605 | Rank is the number of independent row or column vectors or also called independent dimensions (thats why this command is sometimes calles dim) 606 | 607 | my $r = $matrix.rank; 608 | 609 | ### [nullity](#numeric-properties) 610 | 611 | Nullity of a matrix is the number of dependent rows or columns (rank + nullity = dim). Or number of dimensions of the kernel (vector space mapped by the matrix into zero). 612 | 613 | my $n = $matrix.nullity; 614 | 615 | ### [determinant](#numeric-properties) 616 | 617 | Only a [square](#is-square) matrice has a defined determinant, which tells the volume, spanned by the row or column vectors. So if the volume is just in one dimension flat, the determinant is zero, and has a kernel (not a full [rank](#rank) - thus is not [invertible](#is-invertable)). 618 | 619 | my $det = $matrix.determinant; 620 | my $d = $matrix.det; # same thing 621 | my $d = ❘ $matrix ❘; # unicode operator shortcut 622 | 623 | ### [minor](#numeric-properties) 624 | 625 | A Minor is the determinant of a [submatrix](#leaving-out-one) (first variant with same 2 scalar arguments a minor method). The two required positional arguments are row and column indices of an existing [element](#element). 626 | 627 | my $m = $matrix.minor(1,2); 628 | 629 | ### [norm](#numeric-properties) 630 | 631 | A norm is a single positive number, which is an abstraction to the concept of size. Most common form for matrices is the p-norm, where in step 1 the absolute value of every [element](#element) is taken to the power of p. The sum of these results is taken to the power of 1/p. The p-q-Norm extents this process. In his step 2 every column-sum is taken to the power of (p/q). In step 3 the sum of these are taken to the power of (1/q). 632 | 633 | my $norm = $matrix.norm( ); # euclidian norm aka L2 (p = 2, q = 2) 634 | my $norm = + $matrix; # context op shortcut 635 | my $norm = ‖ $matrix ‖; # unicode op shortcut 636 | my $norm = $matrix.norm(1); # p-norm aka L1 = sum of all elements absolute values (p = 1, q = 1) 637 | my $norm = $matrix.norm(p:<4>,q:<3>); # p,q - norm, p = 4, q = 3 638 | my $norm = $matrix.norm(p:<2>,q:<2>); # L2 aka Euclidean aka Frobenius norm 639 | my $norm = $matrix.norm('euclidean'); # same thing, more expressive to some 640 | my $norm = $matrix.norm('frobenius'); # same thing, more expressive to some 641 | my $norm = $matrix.norm('max'); # maximum norm - biggest absolute value of a element 642 | $matrix.norm('row-sum'); # row sum norm - biggest abs. value-sum of a row 643 | $matrix.norm('column-sum'); # column sum norm - same column wise 644 | 645 | ### [condition](#numeric-properties) 646 | 647 | Condition number of a matrix is L2 norm * L2 of [inverted](#inverted) matrix. 648 | 649 | my $c = $matrix.condition( ); 650 | 651 | ### [element-type](#numeric-properties) 652 | 653 | #### [narrowest-element-type](#numeric-properties) 654 | 655 | #### [widest-element-type](#numeric-properties) 656 | 657 | Matrix [element](#element)s can be (from most narrow to widest), of type (Bool), (Int), (Num), (Rat), (FatRat) or (Complex). The widest type of any element will returned as type object. 658 | 659 | In the next example the smartmatch returns true, because no element of our default example matrix has wider type than (Int). After such a test all elements can be safely treated as Int or Bool. 660 | 661 | if $matrix.widest-element-type ~~ Int { ... 662 | 663 | You can also check if all elements have the same type: 664 | 665 | if $matrix.widest-element-type eqv $matrix.narrowest-element-type 666 | 667 | [Derived Matrices](#methods) 668 | ---------------------------- 669 | 670 | Single matrices, that can be computed with only our original matrix as input. 671 | 672 | [transposed](#transposed), [negated](#negated), [conjugated](#conjugated), [adjugated](#adjugated), [inverted](#inverted), [reduced-row-echelon-form](#reduced-row-echelon-form) 673 | 674 | ### [transposed](#derived-matrices) 675 | 676 | Returns a new, transposed Matrix, where rows became colums and vice versa. 677 | 678 | Math::Matrix.new([[1,2,3],[3,4,6]]).transposed : 679 | 680 | [[1 2 3] = [[1 4] 681 | [4 5 6]].T [2 5] 682 | [3 6]] 683 | 684 | Math::Matrix.new([[1,2,3],[3,4,6]]).T # same but shorter 685 | 686 | ### [negated](#derived-matrices) 687 | 688 | Creates a matrix where every [element](#element) has the negated value of the original (invertion of sign). 689 | 690 | my $new = $matrix.negated(); # invert sign of all elements 691 | my $neg = - $matrix; # operator alias 692 | 693 | say $neg: -1 -2 694 | -3 -4 695 | 696 | ### [conjugated](#derived-matrices) 697 | 698 | Creates a matrix where every [element](#element) is the complex conjugated of the original. 699 | 700 | my $c = $matrix.conjugated(); # change every value to its complex conjugated 701 | my $c = $matrix.conj(); # short alias (official Perl 6 shortcut for conjugation of numbers) 702 | 703 | say Math::Matrix.new([[1+i,2],[3,4-i]]).conj : 704 | 705 | 1-1i 2 706 | 3 4+1i 707 | 708 | my $ct = $matrix.H; # conjugated and transposed matrix (Hermitian transpose) 709 | 710 | ### [adjugated](#derived-matrices) 711 | 712 | Creates a matrix out of the properly signed [minors](#minor) of the original. It is called adjugate, classical adjoint or sometimes adjunct. 713 | 714 | $matrix.adjugated.say : 715 | 716 | 4 -3 717 | -2 1 718 | 719 | ### [inverted](#derived-matrices) 720 | 721 | Matrices that have a [square](#is-square) form and a full [rank](#rank) can be [inverted](#inverted) (see [is-invertible](#is-invertible)). Inverse matrix regarding to matrix multiplication (see [dot-product](#dot-product)). The dot product of a matrix with it's inverted results in a [identity](#is-identity) matrix (neutral element in this group). 722 | 723 | my $i = $matrix.inverted(); # invert matrix 724 | my $i = $matrix ** -1; # operator alias 725 | 726 | say $i: 727 | 728 | -2 1 729 | 1.5 -0.5 730 | 731 | ### [reduced-row-echelon-form](#derived-matrices) 732 | 733 | Return the reduced row echelon form of a matrix, a.k.a. row canonical form 734 | 735 | my $rref = $matrix.reduced-row-echelon-form(); 736 | my $rref = $matrix.rref(); # short alias 737 | 738 | [Decompositions](#methods) 739 | -------------------------- 740 | 741 | Methods that return a list of matrices, which can be recombined into the original matrix (mostly by [dot product](#dot-product)). 742 | 743 | [LU](#LU-decomposition), [LU-Crout](#LU-Crout-decomposition), [LDU](#LDU-decomposition), [LUP](#LUP-decomposition), [Cholesky](#Cholesky-decomposition) [LDL](#LDL-decomposition) 744 | 745 | ### [LU-decomposition](#decompositions) 746 | 747 | Decompose an [invertible](#is-invertible), matrix into a [lower triangular](#is-lower-triangular) (called L) and an [upper triangular matrix](#is-upper-triangular) (called U). The algorithm works along the lines of what is called Gaussian elimination. 748 | 749 | my ($L, $U) = $matrix.LU-decomposition(); $L dot $U eq $matrix; # True $U.is-triangular(:upper); # True $L.is-triangular(:lower, :unit); # True 750 | 751 | #### [LU-Crout-decomposition](#decompositions) 752 | 753 | L will also be [unit triangular matrix](#unit-triangular), but not U - meaning the [diagonal](#diagonal) elements of L are all equal 1 and the diagonal elements of U might differ from 1. If you prefer this to be the other way around, use the optional, boolean parameter *:Crout*. 754 | 755 | my ($L, $U) = $matrix.LU-decomposition(:Crout); $L dot $U eq $matrix; # True $L.is-triangular(:lower); # True $U.is-triangular(:upper, :unit); # True 756 | 757 | #### [LDU-decomposition](#decompositions) 758 | 759 | If you want the diagonal elements separated into its own matrix, use the optional, boolean parameter *:diagonal*. Note that you can not use this and *:Crout* at once. 760 | 761 | my ($L, $D, $U) = $matrix.LU-decomposition( :diagonal); $L dot $D dot $U eq $matrix; # True $L.is-triangular(:lower, :unit); # True $U.is-triangular(:upper, :unit); # True $D.is-diagonal(); # True 762 | 763 | #### [LUP-decomposition](#decompositions) 764 | 765 | If you choose the optional, boolean parameter *:pivot*, you get additionally a permutation matrix, that contains the information which rows and columns were swapped, in order to achieve a decomposition. That is why the matrix no longer has to be [invertible](#is-invertable), but only [square](#is-square) to run a LU decomposition with permutation. This option might be combined with *:Crout* or *:diagonal*, but not both. 766 | 767 | my ($L, $U, $P) = $matrix.LU-decomposition( :pivot ); 768 | $L dot $U eq $matrix dot $P; # True 769 | 770 | ### [Cholesky-decomposition](#decompositions) 771 | 772 | This decomposition does roughly the same as LU and is faster. But it works only on matrices that are [symmetric](#is-symmetric) and [positive-definite](#is-positive-definite). The result will be an [orthogonal](#is-orthogonal), [lower triangular matrix](#is-lower-triangular) matrix (here called G) that multiplied with its [transposed](#transposed) gives you the original matrix. 773 | 774 | my $G = $matrix.Cholesky-decomposition( ); # $G is a left triangular matrix 775 | my $G = $matrix.Cholesky-decomposition(:!diagonal); # same as before 776 | $G dot $G.T == $matrix; # True 777 | 778 | #### [LDL-decomposition](#decompositions) 779 | 780 | When the optional, boolean parameter :diagonal is positive (negative by default) you get two matrices (L and D) as a result. L is then a [lower unit triangular matrix](#unit-triangular), (with ones in its main diagonal). D is a [diagonal](#diagonal) matrix ( G = L * sqrt(D)), containing the values formerly on the diagonal of G, but squared. This output format is also known as *LDL decomposition*. You get the second L matrix easily by transposing the L you got. 781 | 782 | my ($L, $D) = $matrix.Cholesky-decomposition(:diagonal); 783 | $L dot $D dot $L.T == $matrix; # True 784 | 785 | [Mathematical Operations](#methods) 786 | ----------------------------------- 787 | 788 | Math methods that work on a whole matrix or just some parts of it. The operands will not be changed but a the result matrix will be returned. 789 | 790 | [equal](#equal), **[add](#add)**: ([matrix](#add-matrix), [vector](#add-vector), [scalar](#add-scalar)), **[multiply](#multiply)**: ([matrix](#multiply-matrix), [vector](#multiply-vector), [scalar](#multiply-scalar)), [dot-product](#dot-product), [tensor-product](#tensor-product). 791 | 792 | ### [equal](#mathematical-operations) 793 | 794 | Checks two matrices for equality. They have to be of same [size](#size) and every [element](#element) of the first matrix on a particular position has to be numerically equal (as checked by *==*) to the element (on the same position) of the second matrix. 795 | 796 | if $matrixa.equal( $matrixb ) { # method variant 797 | if $matrixa == $matrixb { # operator alias 798 | if $matrixa ~~ $matrixb { # smart match redirects to == 799 | 800 | ### [add](#mathematical-operations) 801 | 802 | Adding a matrix, [vector](#add-vector) or [scalar](#scalar-scalar). Named arguments *:row* and *:column* are only used to add a vector (one) or scalar (both). 803 | 804 | #### [add matrix](#add) 805 | 806 | When adding two matrices, they have to be of the same [size](#size). Instead of a Math::Matrix object you can also provide the content of a matrix as [new []](#new--), [new ()](#new---1) or [new ""](#new---2) would accept it. 807 | 808 | $matrix.add( $matrix2 ); 809 | $matrix.add( [[2,3],[4,5]] ); # data alias 810 | $matrix + $matrix2 # operator alias 811 | 812 | Example: 1 2 + 2 3 = 3 5 813 | 3 4 4 5 7 9 814 | 815 | #### [add vector](#add) 816 | 817 | To add a vector you have to specify to which row or column it should be added. The other argument is a list or array (which have to fit the row or column size). 818 | 819 | $matrix.add( row => 1, [2,3] ); 820 | 821 | Example: 1 2 + = 1 2 822 | 3 4 2 3 5 7 823 | 824 | $matrix.add( column => 1, (2,3) ); 825 | 826 | Example: 1 2 + 2 = 1 4 827 | 3 4 3 3 7 828 | 829 | #### [add scalar](#add) 830 | 831 | When adding a single number to the matrix (providing one argument), the number will be added to every [element](#element) of the matrix. If you additionally provide a row or column number, only in that row or column, every element gets added to. In case you provide row and column numbber (three args), only a single element of the result matrix might be different. 832 | 833 | $matrix.add( $number ); # adds number to every element 834 | $matrix + $number; # works too 835 | 836 | Example: 1 2 + 5 = 6 7 837 | 3 4 8 9 838 | 839 | $matrix.add( row => 1, 3 ): [[1,2],[6,7]] 840 | $matrix.add( row => 1, column=> 0, 2 ): [[1,2],[5,4]] 841 | 842 | ### [multiply](#mathematical-operations) 843 | 844 | Unlike the [dot-product](#dot-product) and [tensor-product](#tensor-product), this operation is the simple, scalar multiplication applied to each ore some [element](#element)s. The second factor might come from the cells of another [matrix](#multiply-matrix), a [vector](#multiply-vector) or a [single value](#multiply-scalar). That is why this method works analogous to [add](#add). 845 | 846 | #### [multiply matrix](#multiply) 847 | 848 | When a matrix of same size is given, the result will be a matrix of that size again. Each element will be the product of two the two elements of the operands with the same indices (position). Instead of a Math::Matrix object you can also provide the content of a matrix as [new []](#new--), [new ()](#new---1) or [new ""](#new---2) would accept it. 849 | 850 | my $product = $matrix.multiply( $matrix2 ); # element wise multiplication of same size matrices 851 | my $p = $matrix * $matrix2; # works too 852 | 853 | Example: 1 2 * 2 3 = 2 6 854 | 3 4 4 5 12 20 855 | 856 | #### [multiply vector](#multiply) 857 | 858 | Takes a vector (list of numbers) and a Pair thst specifies an existing row or column. This row or column will than multiplied with the vector (element one by vector element one, and so on). 859 | 860 | my $product = $matrix.multiply( row => 0, (2, 3) ); # multiply every element with number 861 | 862 | 863 | Example: 1 2 * 2 3 = 2 6 864 | 3 4 3 4 865 | 866 | #### [multiply scalar](#multiply) 867 | 868 | If you provide just one number, every matrix element will be multiplied by this number. When you additionally add a valid row or column index, only the cells in that row or column get multiplied. (This is especially useful, when doing Gaussian elimination.) If both are provided, only one cell gets multiplied. 869 | 870 | my $product = $matrix.multiply( $number ); # multiply every element with number 871 | my $p = $matrix * $number; # does the same 872 | 873 | Example: 1 2 * 5 = 5 10 874 | 3 4 15 20 875 | 876 | $matrix.multiply( row => 0, 2); 877 | 878 | Example: 1 2 * 2 = 2 4 879 | 3 4 3 4 880 | 881 | $matrix.multiply(2, column => 0 ); 882 | 883 | Example: 1 2 = 2 2 884 | 3 4 6 4 885 | 886 | *2 887 | 888 | $matrix.multiply(row => 1, column => 1, 3) : [[1,2],[3,12]] 889 | 890 | ### [dot-product](#mathematical-operations) 891 | 892 | Matrix multiplication of two fitting matrices (colums left == rows right). 893 | 894 | Math::Matrix.new( [[1,2],[3,4]] ).dot-product( Math::Matrix.new([[2,3],[4,5]]) ); 895 | 896 | Example: 2 3 897 | 4 5 898 | * 899 | 1 2 10 13 = 1*2+2*4 1*3+2*5 900 | 3 4 22 29 3*2+4*4 3*3+4*5 901 | 902 | my $product = $matrix1.dot-product( $matrix2 ) 903 | my $c = $a dot $b; # works too as operator alias 904 | my $c = $a ⋅ $b; # unicode operator alias 905 | 906 | A shortcut for multiplication is the power - operator ** 907 | my $c = $a ** 3; # same as $a dot $a dot $a 908 | my $c = $a ** -3; # same as ($a dot $a dot $a).inverted 909 | my $c = $a ** 0; # created an right sized identity matrix 910 | 911 | ### [tensor-product](#mathematical-operations) 912 | 913 | The *tensor product* (a.k.a *Kronecker product*) between a matrix A of *size|#size* (m,n) and matrix B of size (p,q) is a matrix C of size (m*p,n*q). C is a concatination of matrices you get if you take every [element](#element) of A and do a [scalar multiplication](#multiply) with B as in $B.multiply($A.element(..,..)). 914 | 915 | Example: 1 2 X* 2 3 = 1*[2 3] 2*[2 3] = 2 3 4 6 916 | 3 4 4 5 [4 5] [4 5] 4 5 8 10 917 | 3*[2 3] 4*[2 3] 6 9 8 12 918 | [4 5] [4 5] 8 15 16 20 919 | 920 | my $c = $matrixa.tensor-product( $matrixb ); 921 | my $c = $a X* $b; # works too as operator alias 922 | my $c = $a ⊗ $b; # unicode operator alias 923 | 924 | [List Like Operations](#methods) 925 | -------------------------------- 926 | 927 | Methods (or extensions thereof) that usually are provided by Lists and Arrays, but make also sense in context of matrices: [elems](#elems), [elem](#elem), [cont](#cont), [map](#map), [map-with-index](#map-with-index), [reduce](#reduce), [reduce-rows](#reduce-rows), [reduce-columns](#reduce-columns) 928 | 929 | ### [elems](#list-like-operations) 930 | 931 | Number (count) of [element](#element)s = rows * columns (see [size](#size)). 932 | 933 | say $matrix.elems(); 934 | 935 | ### [elem](#list-like-operations) 936 | 937 | Asks if all [element](#element) of Matrix (cell) values are an element of the given set or range. 938 | 939 | Math::Matrix.new([[1,2],[3,4]]).elem(1..4) : True 940 | Math::Matrix.new([[1,2],[3,4]]).elem(2..5) : False, 1 is not in 2..5 941 | 942 | ### [cont](#list-like-operations) 943 | 944 | Asks if the matrix contains a value equal to the only argument of the method. If a range is provided as argument, at least one value has to be within this range to make the result true. 945 | 946 | Math::Matrix.new([[1,2],[3,4]]).cont(1) : True 947 | Math::Matrix.new([[1,2],[3,4]]).cont(5) : False 948 | Math::Matrix.new([[1,2],[3,4]]).cont(3..7): True 949 | 950 | MM [[1,2],[3,4]] (cont) 1 # True too 951 | 952 | ### [map](#list-like-operations) 953 | 954 | Creates a new matrix of same size by iterating over all or some [element](#element)s. For every chosen element with the indices (m,n), a provided code block (required argument) will be run once. That block will be given the elements(m,n) value as an argument. The return value of the block will be the content of the element(m,n) of the resulting matrix. 955 | 956 | say $matrix.map(* + 1) : 957 | 958 | 2 3 959 | 4 5 960 | 961 | By provding values (Ranges) to the named arguments *rows* and *columns* (no special order required), only a subset of rows or columns will be mapped - the rest will be just copied. 962 | 963 | say $matrix.map( rows => (0..0), {$_ * 2}) : 964 | 965 | 2 4 966 | 3 4 967 | 968 | say $matrix.map( rows => (1..*), columns => (1..1), {$_ ** 2}) : 969 | 970 | 1 2 971 | 3 16 972 | 973 | ### [map-with-index](#list-like-operations) 974 | 975 | Works just like [map](#map) with the only difference that the given block can recieve one to three arguments: (row index, column index and cell value). 976 | 977 | say $matrix.map-with-index: {$^m == $^n ?? $^value !! 0 } : 978 | 979 | 1 0 980 | 0 4 981 | 982 | ### [reduce](#list-like-operations) 983 | 984 | Like the built in reduce method, it iterates over all [element](#element)s and joins them into one value, by applying the given operator or method to the previous result and the next element. I starts with the element [0][0] and moving from left to right in the first row and continue with the first element of the next row. 985 | 986 | Math::Matrix.new([[1,2],[3,4]]).reduce(&[+]): 10 = 1 + 2 + 3 + 4 987 | Math::Matrix.new([[1,2],[3,4]]).reduce(&[*]): 10 = 1 * 2 * 3 * 4 988 | 989 | ### [reduce-rows](#list-like-operations) 990 | 991 | Reduces (as described above) every row into one value, so the overall result will be a list. In this example we calculate the sum of all elements in a row: 992 | 993 | say Math::Matrix.new([[1,2],[3,4]]).reduce-rows(&[+]): (3, 7) 994 | 995 | ### [reduce-columns](#list-like-operations) 996 | 997 | Similar to reduce-rows, this method reduces each column to one value in the resulting list: 998 | 999 | say Math::Matrix.new([[1,2],[3,4]]).reduce-columns(&[*]): (3, 8) 1000 | 1001 | [Structural Operations](#methods) 1002 | --------------------------------- 1003 | 1004 | Methods that reorder rows and columns, delete some or even add new. The accessor [submatrix](#submatrix) is also useful for that purpose. [move-row](#move-row), [move-column](#move-column), [swap-rows](#swap-rows), [swap-rows](#swap-columns), [splice-rows](#splice-rows), [splice-columns](#splice-columns) 1005 | 1006 | ### [move-row](#structural-matrix-operations) 1007 | 1008 | Math::Matrix.new([[1,2,3],[4,5,6],[7,8,9]]).move-row(0,1); # move row 0 to 1 1009 | Math::Matrix.new([[1,2,3],[4,5,6],[7,8,9]]).move-row(0=>1); # same 1010 | 1011 | 1 2 3 4 5 6 1012 | 4 5 6 ==> 1 2 3 1013 | 7 8 9 7 8 9 1014 | 1015 | ### [move-column](#structural-matrix-operations) 1016 | 1017 | Math::Matrix.new([[1,2,3],[4,5,6],[7,8,9]]).move-column(2,1); 1018 | Math::Matrix.new([[1,2,3],[4,5,6],[7,8,9]]).move-column(2=>1); # same 1019 | 1020 | 1 2 3 1 3 2 1021 | 4 5 6 ==> 4 6 5 1022 | 7 8 9 7 9 8 1023 | 1024 | ### [swap-rows](#structural-matrix-operations) 1025 | 1026 | Math::Matrix.new([[1,2,3],[4,5,6],[7,8,9]]).swap-rows(2,0); 1027 | 1028 | 1 2 3 7 8 9 1029 | 4 5 6 ==> 4 5 6 1030 | 7 8 9 1 2 3 1031 | 1032 | ### [swap-columns](#structural-matrix-operations) 1033 | 1034 | Math::Matrix.new([[1,2,3],[4,5,6],[7,8,9]]).swap-columns(0,2); 1035 | 1036 | 1 2 3 3 2 1 1037 | 4 5 6 ==> 6 5 4 1038 | 7 8 9 9 8 7 1039 | 1040 | ### [splice-rows](#structural-matrix-operations) 1041 | 1042 | Like the splice for lists: the first two parameter are position and amount (optional) of rows to be deleted. The third and alos optional parameter will be an array of arrays (line .new would accept), that fitting row lengths. These rows will be inserted before the row with the number of first parameter. The third parameter can also be a fitting Math::Matrix. 1043 | 1044 | $matrix.splice-rows(0,0, Math::Matrix.new([[5,6],[7,8]]) ); # aka prepend 1045 | $matrix.splice-rows(0,0, [[5,6],[7,8]] ); # same result 1046 | 1047 | 5 6 1048 | 7 8 1049 | 1 2 1050 | 3 4 1051 | 1052 | $matrix.splice-rows(1,0, Math::Matrix.new([[5,6],[7,8]]) ); # aka insert 1053 | $matrix.splice-rows(1,0, [[5,6],[7,8]] ); # same result 1054 | 1055 | 1 2 1056 | 5 6 1057 | 7 8 1058 | 3 4 1059 | 1060 | $matrix.splice-rows(1,1, Math::Matrix.new([[5,6],[7,8]]) ); # aka replace 1061 | $matrix.splice-rows(1,1, [[5,6],[7,8]] ); # same result 1062 | 1063 | 1 2 1064 | 5 6 1065 | 7 8 1066 | 1067 | $matrix.splice-rows(2,0, Math::Matrix.new([[5,6],[7,8]]) ); # aka append 1068 | $matrix.splice-rows(2,0, [[5,6],[7,8]] ); # same result 1069 | $matrix.splice-rows(-1,0, [[5,6],[7,8]] ); # with negative index 1070 | 1071 | 1 2 1072 | 3 4 1073 | 5 6 1074 | 7 8 1075 | 1076 | ### [splice-columns](#structural-matrix-operations) 1077 | 1078 | Same as splice-rows, just horizontally. 1079 | 1080 | $matrix.splice-columns(2,0, Math::Matrix.new([[5,6],[7,8]]) ); # aka append 1081 | $matrix.splice-columns(2,0, [[5,6],[7,8]] ); # same result 1082 | $matrix.splice-columns(-1,0, [[5,6],[7,8]] ); # same result with negative index 1083 | 1084 | 1 2 ~ 5 6 = 1 2 5 6 1085 | 3 4 7 8 3 4 7 8 1086 | 1087 | [Shortcuts](#methods) 1088 | --------------------- 1089 | 1090 | Summary of all shortcut aliases (left) and their long form (right). 1091 | 1092 | * T --> [transposed](#transposed) 1093 | 1094 | * H --> [conjugated and transposed](#conjugated) 1095 | 1096 | * conj --> [conjugated](#conjugated) 1097 | 1098 | * det --> [determinant](#determinant) 1099 | 1100 | * rref --> [reduced-row-echelon-form](#reduced-row-echelon-form) 1101 | 1102 | [Operator Methods](#methods) 1103 | ---------------------------- 1104 | 1105 | Operators (left) with the methods they refer to (right). (Most ops are just aliases.) For more explanations of the ops (with examples) see the [next chapter](#operators): 1106 | 1107 | * prefix ? --> [Bool](#bool) 1108 | 1109 | * prefix + --> [Numeric](#numeric) 1110 | 1111 | * prefix - --> [negated](#negated) 1112 | 1113 | * prefix ~ --> [Str](#str) 1114 | 1115 | * prefix | --> [list](#list) 1116 | 1117 | * prefix @ --> [Array](#array) 1118 | 1119 | * prefix % --> [Hash](#hash) 1120 | 1121 | * prefix MM --> [new](#new--) 1122 | 1123 | * infix == --> [equal](#equal) 1124 | 1125 | * infix ~~ --> [equal](#equal) ACCEPTS 1126 | 1127 | * infix + --> [add](#add) 1128 | 1129 | * infix - --> [add](#add) 1130 | 1131 | * infix * --> [multiply](#multiply) 1132 | 1133 | * infix ⋅ dot --> [dot-product](#dot-product) 1134 | 1135 | * infix ÷ --> dot-product [inverted](#inverted) 1136 | 1137 | * infix ** --> dot-product inverted 1138 | 1139 | * infix ⊗ X* --> [tensor-product](#tensor-product) 1140 | 1141 | * circumfix |..| --> [determinant](#determinant) 1142 | 1143 | * circumfix ‖..‖ --> [norm](#norm) 1144 | 1145 | * postcircumfix [..] --> [AT-POS](#at-pos) 1146 | 1147 | [Operators](#synopsis) 1148 | ====================== 1149 | 1150 | The Module overloads or introduces a range of well and lesser known ops, which are almost all [aliases](#operator-methods). 1151 | 1152 | ==, +, * are commutative, -, ⋅, dot, ÷, x, ⊗ and ** are not. All ops have same precedence as its multi method siblings - unless stated otherwise. 1153 | 1154 | They are exported when using no flag (same as :DEFAULT) or :ALL, but not under :MANDATORY or :MM). The only exception is MM operator, a shortcut to create a matrix. That has to be importet explicitly with the tag :MM or :ALL. The postcircumfix [] - op will always work. 1155 | 1156 | my $a = +$matrix # Num context, Euclidean norm 1157 | my $b = ?$matrix # Bool context, True if any element has a none zero value 1158 | my $str = ~$matrix # String context, matrix content, space and new line separated as table 1159 | my $l = |$matrix # list context, list of all elements, row-wise 1160 | my $a = @ $matrix # same thing, but as Array 1161 | my $h = % $matrix # hash context, similar to .kv, so that %$matrix{0}{0} is first element 1162 | 1163 | $matrixa == $matrixb # check if both have same size and they are element wise equal 1164 | $matrixa ~~ $matrixb # same thing 1165 | 1166 | my $sum = $matrixa + $matrixb; # element wise sum of two same sized matrices 1167 | my $sum = $matrix + $number; # add number to every element 1168 | 1169 | my $dif = $matrixa - $matrixb; # element wise difference of two same sized matrices 1170 | my $dif = $matrix - $number; # subtract number from every element 1171 | my $neg = -$matrix # negate value of every element 1172 | 1173 | my $p = $matrixa * $matrixb; # element wise product of two same sized matrices 1174 | my $sp = $matrix * $number; # multiply number to every element 1175 | 1176 | my $dp = $a dot $b; # dot product of two fitting matrices (cols a = rows b) 1177 | my $dp = $a ⋅ $b; # dot product, unicode (U+022C5) 1178 | my $dp = $a ÷ $b; # alias to $a dot $b.inverted, (U+000F7) 1179 | 1180 | my $c = $a ** 3; # $a to the power of 3, same as $a dot $a dot $a 1181 | my $c = $a ** -3; # alias to ($a dot $a dot $a).inverted 1182 | my $c = $a ** 0; # creats an right sized identity matrix 1183 | 1184 | my $tp = $a X* $b; # tensor product, same precedence as infix: x (category Replication) 1185 | my $tp = $a ⊗ $b; # tensor product, unicode (U+02297) 1186 | 1187 | |$matrix | # determinant, unicode (U+0FF5C) 1188 | ‖ $matrix ‖ # L2 norm (euclidean p=2 to the square), (U+02016) 1189 | 1190 | $matrix[1][2] # 2nd row, 3rd column element - works even under :MANDATORY tag 1191 | 1192 | MM [[1]] # a new matrix, has higher precedence than postcircumfix:[] 1193 | MM '1' # string alias 1194 | 1195 | [Export Tags](#synopsis) 1196 | ======================== 1197 | 1198 | * :MANDATORY (nothing is exported) 1199 | 1200 | * :DEFAULT (same as no tag, most [ops](#operators) will be exported) 1201 | 1202 | * :MM (only [MM](#new--) op exported) 1203 | 1204 | * :ALL 1205 | 1206 | [Authors](#synopsis) 1207 | ==================== 1208 | 1209 | * Pierre VIGIER 1210 | 1211 | * Herbert Breunung 1212 | 1213 | [Contributors](#synopsis) 1214 | ========================= 1215 | 1216 | * Patrick Spek 1217 | 1218 | * Juan Julián Merelo Guervós 1219 | 1220 | [License](#synopsis) 1221 | ==================== 1222 | 1223 | Artistic License 2.0 (GPL and BSD at the same time) 1224 | 1225 | [See Also](#synopsis) 1226 | ===================== 1227 | 1228 | * Math::Libgsl::Matrix 1229 | 1230 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: Visual Studio 2015 2 | 3 | platform: x64 4 | 5 | build: off 6 | 7 | environment: 8 | matrix: 9 | - test_moar: '2018.01' 10 | # - test_moar: '' #latest 11 | 12 | install: 13 | - '"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64' 14 | - choco install strawberryperl --allow-empty-checksums 15 | - SET PATH=C:\strawberry\c\bin;C:\strawberry\perl\site\bin;C:\strawberry\perl\bin;%PATH% 16 | - git clone https://github.com/tadzik/rakudobrew %USERPROFILE%\rakudobrew 17 | - SET PATH=%USERPROFILE%\rakudobrew\bin;%PATH% 18 | - rakudobrew build moar 2018.01 19 | - rakudobrew build zef 20 | - cd %APPVEYOR_BUILD_FOLDER% 21 | - zef install Test::META 22 | - zef install AttrX::Lazy 23 | - SET PATH=C:\Users\appveyor\rakudobrew\moar-%TEST_MOAR%\install\share\perl6\site\bin;%PATH% 24 | - SET AUTHOR_TESTING=1 25 | - zef --depsonly install . 26 | 27 | test_script: 28 | - set TEST_AUTHOR=1 && prove -ve "perl6 -Ilib" 29 | - zef --debug install . 30 | 31 | shallow_clone: true 32 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | ; dist.ini 2 | name = Math-Matrix 3 | 4 | [ReadmeFromPod] 5 | filename = lib/Math/Matrix.pod 6 | 7 | [PruneFiles] 8 | match = ^ 'xt/' 9 | -------------------------------------------------------------------------------- /lib/Math/Matrix.pm6: -------------------------------------------------------------------------------- 1 | use v6.c; 2 | 3 | use Math::Matrix::Util; 4 | use Math::Matrix::Type; 5 | 6 | unit class Math::Matrix:ver<0.3.9>:auth does Math::Matrix::Util; 7 | use AttrX::Lazy; 8 | use Math::Matrix::ArrayOfArray; 9 | 10 | ################################################################################ 11 | # attributes 12 | ################################################################################ 13 | 14 | has @!rows is required; # primary content 15 | has Int $!row-count is required; 16 | has Int $!column-count is required; 17 | 18 | has Bool $!is-zero is lazy; 19 | has Bool $!is-identity is lazy; 20 | has Bool $!is-square is lazy; 21 | has Bool $!is-diagonal is lazy; 22 | has Bool $!is-anti-diagonal is lazy; 23 | has Bool $!is-diagonal-constant is lazy; 24 | has Bool $!is-main-diagonal-constant is lazy; 25 | has Bool $!is-catalecticant is lazy; 26 | has Bool $!is-symmetric is lazy; 27 | has Bool $!is-antisymmetric is lazy; 28 | has Bool $!is-self-adjoint is lazy; 29 | has Bool $!is-unitary is lazy; 30 | has Bool $!is-orthogonal is lazy; 31 | has Bool $!is-invertible is lazy; 32 | has Bool $!is-positive-definite is lazy; 33 | has Bool $!is-positive-semidefinite is lazy; 34 | 35 | has Int $!upper-bandwith is lazy; 36 | has Int $!lower-bandwith is lazy; 37 | has Int $!rank is lazy; 38 | has Int $!nullity is lazy; 39 | has Rat $!density is lazy; 40 | has Numeric $!trace is lazy; 41 | has Numeric $!determinant is lazy; 42 | has Numeric $!condition is lazy; 43 | has Numeric $!narrowest-element-type is lazy; 44 | has Numeric $!widest-element-type is lazy; 45 | 46 | has Str $!gist; 47 | has Rat @!eigenvalues; 48 | 49 | ################################################################################ 50 | # private accessors - interface to util role 51 | ################################################################################ 52 | 53 | method !rows { @!rows } 54 | method !row-count { $!row-count } 55 | method !column-count{ $!column-count } 56 | method !clone-cells { Math::Matrix::ArrayOfArray::clone(@!rows) } 57 | 58 | ################################################################################ 59 | # public methods: constructors 60 | ################################################################################ 61 | 62 | multi method new( @m ) { 63 | Math::Matrix::ArrayOfArray::check-data( @m ); 64 | self.bless( rows => @m ); 65 | } 66 | multi method new (Str $m){ 67 | Math::Matrix.new( $m.lines.map: { .words.map: {.Bool.Str eq $_ ?? .Bool !! .Numeric} } ); 68 | } 69 | 70 | submethod BUILD( :@rows!, :$density, :$trace, :$determinant, :$rank, :$nullity, 71 | :$is-zero, :$is-identity, :$is-symmetric, :$is-invertible) { 72 | @!rows = Math::Matrix::ArrayOfArray::clone(@rows); 73 | $!row-count = @rows.elems; 74 | $!column-count = @rows[0].elems; 75 | $!density = $density if $density.defined; 76 | $!trace = $trace if $trace.defined; 77 | $!determinant = $determinant if $determinant.defined; 78 | $!rank = $rank if $rank.defined; 79 | $!nullity = $nullity if $nullity.defined; 80 | $!is-zero = $is-zero if $is-zero.defined; 81 | $!is-identity = $is-identity if $is-identity.defined; 82 | $!is-symmetric = $is-symmetric if $is-symmetric.defined; 83 | $!is-invertible= $is-invertible if $is-invertible.defined; 84 | } 85 | 86 | method clone { self.bless( rows => self!clone-cells() ) } 87 | 88 | multi method new-zero(PosInt $size) { 89 | self.bless( rows => Math::Matrix::ArrayOfArray::new-zero($size, $size), 90 | determinant => 0, rank => 0, nullity => $size, density => 0.0, trace => 0, 91 | is-zero => True, is-identity => False, is-diagonal => True, 92 | is-square => True, is-symmetric => True, is-invertible => False ); 93 | } 94 | multi method new-zero(Math::Matrix:U: PosInt $rows, PosInt $cols) { 95 | self.bless( rows => Math::Matrix::ArrayOfArray::new-zero($rows, $cols), 96 | determinant => 0, rank => 0, nullity => min($rows, $cols), density => 0.0, trace => 0, 97 | is-zero => True, is-identity => False, is-diagonal => ($cols == $rows), is-invertible => False ); 98 | } 99 | 100 | method new-identity( Int $size where * > 0 ) { 101 | self.bless( rows => Math::Matrix::ArrayOfArray::new-identity($size), 102 | determinant => 1, rank => $size, nullity => 0, density => 1/$size, trace => $size, 103 | is-zero => False, is-identity => True, 104 | is-square => True, is-diagonal => True, is-symmetric => True, is-invertible => True ); 105 | } 106 | 107 | method new-diagonal( *@diag ){ 108 | fail "Expect at least on number as parameter" if @diag == 0; 109 | fail "Expect an List of Number" unless @diag ~~ NumList; 110 | my Int $size = +@diag; 111 | my $det = [*](@diag.flat); 112 | self.bless( rows => Math::Matrix::ArrayOfArray::new-diagonal(@diag), 113 | determinant => $det, trace => [+] (@diag.flat), 114 | is-zero => False, is-square => True, is-diagonal => True, is-symmetric => True, 115 | is-invertible => $det != 0 ); 116 | } 117 | 118 | method new-lower-triangular( @m ) { 119 | #don't want to trust outside of the class that a matrix is really triangular 120 | self.bless( rows => @m ); 121 | } 122 | 123 | method new-upper-triangular( @m ) { 124 | #don't want to trust outside of the class that a matrix is really triangular 125 | self.bless( rows => @m ); 126 | } 127 | 128 | method new-vector-product (@column_vector, @row_vector){ 129 | fail "Expect two Arrays of Number" unless @column_vector ~~ NumArray and @row_vector ~~ NumArray; 130 | my @p; 131 | for ^+@column_vector X ^+@row_vector -> ($r, $c) { 132 | @p[$r][$c] = @column_vector[$r] * @row_vector[$c] 133 | } 134 | my $rank = ( all(@column_vector) ~~ 0 or all(@row_vector) ~~ 0) ?? 0 !! 1; 135 | self.bless( rows => @p, determinant => 0 , rank => $rank, 136 | is-invertible => (@column_vector.elems == @row_vector.elems and $rank >= @row_vector.elems) ); 137 | } 138 | 139 | ################################################################################ 140 | # end of constructor - start accessors 141 | ################################################################################ 142 | 143 | method element(Math::Matrix:D: Int:D $row, Int:D $column --> Numeric ) { 144 | self!check-index($row, $column); 145 | @!rows[$row][$column]; 146 | } 147 | 148 | multi method AT-POS (Math::Matrix:D: Int:D $row){ 149 | self!check-row-index($row); 150 | @!rows[$row]; 151 | } 152 | 153 | method row(Math::Matrix:D: Int:D $row --> List) { 154 | self!check-row-index($row); 155 | @!rows[$row]; 156 | } 157 | 158 | method column(Math::Matrix:D: Int:D $column --> List) { 159 | self!check-column-index($column); 160 | (@!rows.keys.map:{ @!rows[$_;$column] }).list; 161 | } 162 | 163 | method diagonal(Math::Matrix:D: $start? = 0 --> List){ 164 | fail "requested diagonal is outside of matrix boundaries" if $start >= $!row-count or $start <= -$!column-count; 165 | ($start > 0 ?? map { @!rows[$^i+$start;$^i] }, ^min($!column-count, $!row-count - $start) 166 | !! map { @!rows[$^i;$^i-$start] }, ^min($!row-count, $!column-count + $start) ).list; 167 | } 168 | 169 | method skew-diagonal(Math::Matrix:D: $start? = 0 --> List){ 170 | fail "skew diagonal is only defined for square matrices" unless $.is-square; 171 | fail "requested skew diagonal is outside of matrix boundaries" if $start.abs >= $!row-count; 172 | ($start > 0 ?? map { @!rows[$!row-count -1 -$^i; $start+$^i] }, ^($!row-count - $start) 173 | !! map { @!rows[$!row-count -1 -$^i +$start;$^i] }, ^($!row-count + $start) ).list; 174 | } 175 | 176 | multi method submatrix(Math::Matrix:D: Int:D $row, Int:D $column --> Math::Matrix:D ){ 177 | self!check-index($row, $column); 178 | my @rows = ^$!row-count; @rows.splice($row,1); 179 | my @cols = ^$!column-count; @cols.splice($column,1); 180 | self.submatrix( rows => @rows , columns => @cols); 181 | } 182 | multi method submatrix(Math::Matrix:D: :@rows = (^$!row-count).list, 183 | :@columns = (^$!column-count).list --> Math::Matrix:D) { 184 | my @r = @rows.max == Inf ?? (@rows.min .. $!row-count-1).list !! @rows.list; 185 | my @c = @columns.max == Inf ?? (@columns.min .. $!column-count-1).list !! @columns.list; 186 | fail "Need at least one row number" if @r == 0; 187 | fail "Need at least one column number" if @c == 0; 188 | self!check-indices(@r, @c); 189 | Math::Matrix.new([ @r.map( { [ @!rows[$^row][|@c] ] } ) ]); 190 | } 191 | 192 | ################################################################################ 193 | # end of accessors - start with type conversion and handy shortcuts 194 | ################################################################################ 195 | 196 | method Bool( Math::Matrix:D: --> Bool) { ! self.is-zero } 197 | method Numeric ( Math::Matrix:D: --> Numeric){ self.norm } 198 | method Str( Math::Matrix:D: --> Str) { join("\n", @!rows.map: *.Str) } 199 | method Range( Math::Matrix:D: --> Range) { self.list.minmax } 200 | method Array( Math::Matrix:D: --> Array) { self!clone-cells } 201 | method list( Math::Matrix:D: --> List) { self.list-rows.flat.list } 202 | method list-rows( Math::Matrix:D: --> List) { (@!rows.map: {.flat}).list } 203 | method list-columns(Math::Matrix:D: --> List) { ((^$!column-count).map: {self.column($_)}).list } 204 | method Hash( Math::Matrix:D: --> Hash) { ((^$!row-count).map: {$_ => @!rows[$_].kv.Hash}).Hash} 205 | 206 | multi method gist(Math::Matrix:U: --> Str) { "({self.^name})" } 207 | multi method gist(Math::Matrix:D: Int :$max-chars?, Int :$max-rows? --> Str) { 208 | if not $!gist.defined or ($max-chars.defined or $max-rows.defined) { 209 | my $max-width = (not $max-chars.defined or $max-chars < 5) ?? 80 !! $max-chars; 210 | my $max-heigth = (not $max-rows.defined or $max-rows < 2) ?? 20 !! $max-rows; 211 | my @fmt-content = @!rows.map: { # all values in optimized complex format 212 | (.map: { $_ ~~ Bool ?? %( re => $_, im => '' ) !! 213 | $_ ~~ Complex ?? %( re => $_.re.fmt("%g"),im => (($_.im >= 0 ??'+'!!'')~$_.im.fmt("%g")~'i') ) !! 214 | %( re => $_.fmt("%g"), im => '' ) 215 | }).Array}; 216 | my @col-width; # width of the formatted element content in n column 217 | @fmt-content.map: { 218 | for .kv -> $ci, $val { 219 | @col-width[$ci].push: $val.chars; 220 | @col-width[$ci].push: $val.chars; 221 | }}; 222 | my @max-width = @col-width.map: { %( re => $_.max, im => $_.max ) }; 223 | my ($shown-cols, $width-index); 224 | for @max-width.kv -> $ci, $max { 225 | $width-index += 2 + $max + $max; 226 | if ($ci < @max-width.end and $width-index <= $max-width-3) 227 | or $width-index <= $max-width {$shown-cols++} 228 | else {last} 229 | } 230 | my $shown-rows = min @!rows.elems, $max-heigth; 231 | my $out; 232 | for @fmt-content.kv -> $ri, $row { 233 | if $ri == $shown-rows {$out ~= " ...\n" ; last} 234 | for $row.kv -> $ci, $val { 235 | if $ci == $shown-cols { $out ~= ' ..' ; last} 236 | $out ~= (' ' x (@max-width[$ci] - @col-width[$ci][$ri]) + 2) ~ $val ~ 237 | $val ~ (' ' x (@max-width[$ci] - @col-width[$ci][$ri])); 238 | } 239 | $out ~= "\n"; 240 | }; 241 | $!gist = $out.chomp; 242 | } 243 | $!gist; 244 | } 245 | 246 | multi method perl(Math::Matrix:D: --> Str){ self.WHAT.perl ~ ".new(" ~ @!rows.perl ~ ")" } 247 | 248 | ################################################################################ 249 | # end of type conversion and handy shortcuts - start boolean matrix properties 250 | ################################################################################ 251 | 252 | method !build_is-square( Math::Matrix:D: --> Bool) {$!column-count == $!row-count} 253 | method !build_is-zero( Math::Matrix:D: --> Bool) {self.density() == 0} 254 | method !build_is-identity( Math::Matrix:D: --> Bool) {$.is-diagonal and $.is-main-diagonal-constant and @!rows[0][0] == 1} 255 | method !build_is-diagonal( Math::Matrix:D: --> Bool) {self.is-square and $.lower-bandwith == $.upper-bandwith == 0} 256 | method !build_is-main-diagonal-constant(Math::Matrix:D: --> Bool){ [==](($.diagonal.flat).flat) } 257 | 258 | method !build_is-diagonal-constant( Math::Matrix:D: --> Bool) { 259 | [&&](map { [==] $.diagonal($_).list }, -$!column-count+1 .. $!row-count-1); 260 | } 261 | method !build_is-catalecticant( Math::Matrix:D: --> Bool) { 262 | $.is-square and [&&](map { [==] $.skew-diagonal($_).list }, -$!column-count+1 .. $!row-count-1); 263 | } 264 | 265 | method is-triangular(Math::Matrix:D: Bool :$strict, Bool :$unit, Bool :$atomic, Bool :$upper, Bool :$lower --> Bool) { 266 | return False unless $.is-square; 267 | return False if $unit and $strict; 268 | return False if $atomic and ($strict or ($unit.defined and not $unit)); 269 | return False if $strict.defined and ($strict xor ($.is-main-diagonal-constant and @!rows[0][0] == 0)); 270 | return False if $unit.defined and ($unit xor ($.is-main-diagonal-constant and @!rows[0][0] == 1)); 271 | return False if $atomic.defined and ($atomic xor self!is-frobenius ); 272 | return False if $upper.defined and ($upper xor $.lower-bandwith == 0); 273 | return False if $lower.defined and ($lower xor $.upper-bandwith == 0); 274 | $.lower-bandwith == 0 or $.upper-bandwith == 0; 275 | } 276 | method !is-frobenius(Math::Matrix:D: --> Bool){ 277 | my $col; 278 | for ^$!row-count X ^$!column-count -> ($r, $c) { 279 | next if $r == $c; 280 | next if @!rows[$r][$c] == 0; 281 | if $col.defined { return False if $col != $c } 282 | else { $col = $c } 283 | } 284 | True; 285 | } 286 | 287 | method !build_is-anti-diagonal(Math::Matrix:D: --> Bool){ 288 | for ^$!row-count X ^$!column-count -> ($r, $c) { 289 | next if $r == $!column-count - $c - 1 or @!rows[$r][$c] == 0; 290 | return False; 291 | } 292 | True; 293 | } 294 | 295 | method is-tridiagonal(Math::Matrix:D: --> Bool) { 296 | $.is-square and $.lower-bandwith < 2 and $.upper-bandwith < 2; 297 | } 298 | 299 | method is-diagonally-dominant( Math::Matrix:D: Bool :$strict = False, 300 | Str :$along where {$^orient eq any } = 'column' --> Bool) { 301 | return False unless self.is-square; 302 | my $greater = $strict ?? &[>] !! &[>=]; 303 | my Bool $colwise; 304 | if $along ~~ any { 305 | $colwise = [and] map {my $c = $_; &$greater( @!rows[$c][$c] * 2, 306 | [+](map {abs $_[$c]}, @!rows)) }, ^$!row-count; 307 | } 308 | return $colwise if $along eq 'column'; 309 | my Bool $rowwise = [and] map { &$greater( @!rows[$^r][$^r] * 2, 310 | [+](map {abs $^c}, @!rows[$^r].flat)) }, ^$!row-count; 311 | return $rowwise if $along eq 'row'; 312 | $colwise and $rowwise; 313 | } 314 | 315 | method !build_is-symmetric( Math::Matrix:D: --> Bool) { 316 | return False unless self.is-square; 317 | return True if $!row-count < 2; 318 | for ^($!row-count - 1) -> $r { 319 | for $r ^..^ $!row-count -> $c { 320 | return False unless @!rows[$r][$c] == @!rows[$c][$r]; 321 | } 322 | } 323 | True; 324 | } 325 | method !build_is-antisymmetric(Math::Matrix:D: --> Bool) { 326 | return False unless self.is-square; 327 | return True if $!row-count < 2; 328 | for ^($!row-count - 1) -> $r { 329 | for $r ^..^ $!row-count -> $c { 330 | return False unless @!rows[$r][$c] == - @!rows[$c][$r]; 331 | } 332 | } 333 | True; 334 | } 335 | 336 | method !build_is-self-adjoint(Math::Matrix:D: --> Bool) { 337 | self.is-square and self.T.conj ~~ self 338 | } 339 | 340 | method !build_is-unitary(Math::Matrix:D: --> Bool) { 341 | self.is-square and self.dot-product( self.T.conj ) ~~ Math::Matrix.new-identity( $!row-count ); 342 | } 343 | 344 | method !build_is-orthogonal(Math::Matrix:D: --> Bool) { 345 | self.is-square and self.dot-product( self.T ) ~~ Math::Matrix.new-identity( $!row-count ); 346 | } 347 | 348 | method !build_is-invertible(Math::Matrix:D: --> Bool) { 349 | self.is-square and self.determinant != 0; 350 | } 351 | 352 | # method is-definite(Math::Matrix:D: Bool :positive, Bool :negative, Bool :semi --> Bool){} 353 | 354 | method !build_is-positive-definite (Math::Matrix:D: --> Bool) { # with Sylvester's criterion 355 | return False unless self.is-square; 356 | return False unless self.determinant > 0; 357 | my $sub = Math::Matrix.new( @!rows ); 358 | for $!row-count - 1 ... 1 -> $r { 359 | $sub = $sub.submatrix(rows => 0..$r, columns => 0..$r); 360 | return False unless $sub.determinant > 0; 361 | } 362 | True; 363 | } 364 | method !build_is-positive-semidefinite (Math::Matrix:D: --> Bool) { # with Sylvester's criterion 365 | return False unless self.is-square; 366 | return False unless self.determinant >= 0; 367 | my $sub = Math::Matrix.new( @!rows ); 368 | for $!row-count - 1 ... 1 -> $r { 369 | $sub = $sub.submatrix(rows => 0..$r, columns => 0..$r); 370 | return False unless $sub.determinant >= 0; 371 | } 372 | True; 373 | } 374 | 375 | ################################################################################ 376 | # end of boolean matrix properties - start numeric matrix properties 377 | ################################################################################ 378 | 379 | method size(Math::Matrix:D: --> List) { $!row-count, $!column-count } 380 | 381 | method !build_density(Math::Matrix:D: --> Rat) { 382 | my $valcount = 0; 383 | for ^$!row-count X ^$!column-count -> ($r, $c) { $valcount++ if @!rows[$r][$c] != 0 } 384 | $valcount / self.elems; 385 | } 386 | 387 | method !build_upper-bandwith(Math::Matrix:D: --> Int) { 388 | for $!column-count-1 ... 1 -> $i { 389 | return $i unless [&&](map * == 0, $.diagonal(-$i).list) 390 | } 391 | 0; 392 | } 393 | method !build_lower-bandwith(Math::Matrix:D: --> Int) { 394 | for $!row-count-1 ... 1 -> $i { 395 | return $i unless [&&](map * == 0, $.diagonal($i).list) 396 | } 397 | 0; 398 | } 399 | method bandwith(Math::Matrix:D: Str $which = '' --> Int) { max $.upper-bandwith, $.lower-bandwith } 400 | 401 | method !build_trace(Math::Matrix:D: --> Numeric) { 402 | fail "trace is only defined for a square matrix" unless self.is-square; 403 | self.diagonal.sum; 404 | } 405 | 406 | method !build_rank(Math::Matrix:D: --> Int) { 407 | my $rank = 0; 408 | my @clone = @!rows.clone(); 409 | for ^$!column-count -> $c { # make upper triangle via gauss elimination 410 | last if $rank == $!row-count; # rank cant get bigger thean dim 411 | my $swap_row_nr = $rank; 412 | $swap_row_nr++ while $swap_row_nr < $!row-count and @clone[$swap_row_nr][$c] == 0; 413 | next if $swap_row_nr == $!row-count; 414 | (@clone[$rank], @clone[$swap_row_nr]) = (@clone[$swap_row_nr], @clone[$rank]); 415 | for $rank + 1 ..^ $!row-count -> $r { 416 | next if @clone[$r][$c] == 0; 417 | my $q = @clone[$rank][$c] / @clone[$r][$c]; 418 | @clone[$r] = @clone[$rank] >>-<< $q <<*<< @clone[$r]; 419 | } 420 | $rank++; 421 | } 422 | $rank; 423 | } 424 | method !build_nullity(Math::Matrix:D: --> Int) { 425 | min(self.size) - self.rank; 426 | } 427 | 428 | method det(Math::Matrix:D: --> Numeric ) { self.determinant } # the usual short name 429 | method !build_determinant(Math::Matrix:D: --> Numeric) { 430 | fail "number of columns has to be same as number of rows" unless self.is-square; 431 | return 1 if $!row-count == 0; 432 | return @!rows[0][0] if $!row-count == 1; 433 | if $!row-count > 4 { 434 | #up to 4x4 naive method is fully usable 435 | return [*] $.diagonal.flat if $.is-triangular(); 436 | try { 437 | my ($L, $U, $P) = $.decompositionLU(); 438 | return $P.inverted.det * $L.det * $U.det; 439 | } 440 | } 441 | my $det = 0; 442 | for ( σ_permutations([^$!row-count]) ) { 443 | my $permutation = .key; 444 | my $product = .value; 445 | for $permutation.kv -> $i, $j { $product *= @!rows[$i][$j] }; 446 | $det += $product; 447 | } 448 | $!determinant = $det; 449 | } 450 | method determinant-naive(Math::Matrix:D: --> Numeric) { 451 | fail "Number of columns has to be same as number of rows" unless self.is-square; 452 | return 1 if $!row-count == 0; 453 | return @!rows[0][0] if $!row-count == 1; 454 | my $det = 0; 455 | for ( σ_permutations([^$!row-count]) ) { 456 | my $permutation = .key; 457 | my $product = .value; 458 | for $permutation.kv -> $i, $j { $product *= @!rows[$i][$j] }; 459 | $det += $product; 460 | } 461 | $det; 462 | } 463 | sub insert ($x, @xs) { ([flat @xs[0 ..^ $_], $x, @xs[$_ .. *]] for 0 .. @xs) } 464 | sub order ($sg, @xs) { $sg > 0 ?? @xs !! @xs.reverse } 465 | 466 | multi sub σ_permutations ([]) { [] => 1 } 467 | multi sub σ_permutations ([$x, *@xs]) { 468 | σ_permutations(@xs).map({ |order($_.value, insert($x, $_.key)) }) Z=> |(1,-1) xx * 469 | } 470 | 471 | method minor(Math::Matrix:D: Int:D $row, Int:D $col --> Numeric) { $.submatrix($row, $col).determinant } 472 | 473 | multi method norm(Math::Matrix:D: PosInt :$p = 2, PosInt :$q = $p --> Numeric) { 474 | my $norm = 0; 475 | for ^$!column-count -> $c { 476 | my $col_sum = 0; 477 | for ^$!row-count -> $r { $col_sum += abs(@!rows[$r][$c]) ** $p } 478 | $norm += $col_sum ** ($q / $p); 479 | } 480 | $norm ** (1/$q); 481 | } 482 | multi method norm(Math::Matrix:D: PosInt $p --> Numeric){ self.norm(:p<$p>,:q<$p>)} 483 | multi method norm(Math::Matrix:D: 'frobenius' --> Numeric){ self.norm(:p<2>, :q<2>)} 484 | multi method norm(Math::Matrix:D: 'euclidean' --> Numeric){ self.norm(:p<2>, :q<2>)} 485 | 486 | multi method norm(Math::Matrix:D: 'max' --> Numeric) { max @!rows.map: {max .map: *.abs} } 487 | multi method norm(Math::Matrix:D: 'row-sum' --> Numeric) { max @!rows.map: {[+] .map: *.abs} } 488 | multi method norm(Math::Matrix:D: 'column-sum'--> Numeric){ max (^$!column-count).map: {[+] self.column($_).map: *.abs} } 489 | 490 | method !build_condition(Math::Matrix:D: --> Numeric) { $.norm() * $.inverted.norm } 491 | 492 | method !build_narrowest-element-type(Math::Matrix:D: --> Numeric){ 493 | return Bool if any( @!rows[*;*] ) ~~ Bool; 494 | return Int if any( @!rows[*;*] ) ~~ Int; 495 | return Num if any( @!rows[*;*] ) ~~ Num; 496 | return Rat if any( @!rows[*;*] ) ~~ Rat; 497 | return FatRat if any( @!rows[*;*] ) ~~ FatRat; 498 | Complex; 499 | } 500 | method !build_widest-element-type(Math::Matrix:D: --> Numeric){ 501 | return Complex if any( @!rows[*;*] ) ~~ Complex; 502 | return FatRat if any( @!rows[*;*] ) ~~ FatRat; 503 | return Rat if any( @!rows[*;*] ) ~~ Rat; 504 | return Num if any( @!rows[*;*] ) ~~ Num; 505 | return Int if any( @!rows[*;*] ) ~~ Int; 506 | Bool; 507 | } 508 | 509 | ################################################################################ 510 | # end of numeric matrix properties - start create derivative matrices 511 | ################################################################################ 512 | 513 | method T( Math::Matrix:D: --> Math::Matrix:D ) { self.transposed } 514 | method transposed(Math::Matrix:D: --> Math::Matrix:D ) { 515 | my @transposed; 516 | for ^$!row-count X ^$!column-count -> ($r, $c) { @transposed[$c][$r] = @!rows[$r][$c] } 517 | Math::Matrix.new( @transposed ); # TODO use bless to preserve properties 518 | } 519 | 520 | method negated(Math::Matrix:D: --> Math::Matrix:D ) { self.map( - * ) } 521 | 522 | method H( Math::Matrix:D: --> Math::Matrix:D ) { self.conjugated.transposed } 523 | 524 | method conj( Math::Matrix:D: --> Math::Matrix:D ) { self.conjugated } 525 | method conjugated(Math::Matrix:D: --> Math::Matrix:D ) { self.map( { $_.conj} ) } 526 | 527 | method adjugated( Math::Matrix:D: --> Math::Matrix:D) { 528 | fail "Number of columns has to be same as number of rows" unless self.is-square; 529 | $!row-count == 1 ?? self.new([[1]]) 530 | !! self.map-index({ self.minor($^m, $^n) * self.cofactor-sign($^m, $^n) }); 531 | } 532 | 533 | method inverted(Math::Matrix:D: --> Math::Matrix:D) { 534 | fail "Number of columns has to be same as number of rows" unless self.is-square; 535 | fail "Matrix is not invertible, or singular because defect (determinant = 0)" if self.determinant == 0; 536 | my @clone = self!clone-cells(); 537 | my @inverted = Math::Matrix::ArrayOfArray::new-identity( $!row-count ); 538 | for ^$!row-count -> $c { 539 | my $swap_row_nr = $c; # make sure that diagonal element != 0, later == 1 540 | $swap_row_nr++ while @clone[$swap_row_nr][$c] == 0; 541 | (@clone[$c], @clone[$swap_row_nr]) = (@clone[$swap_row_nr], @clone[$c]); 542 | (@inverted[$c], @inverted[$swap_row_nr]) = (@inverted[$swap_row_nr], @inverted[$c]); 543 | @inverted[$c] = @inverted[$c] >>/>> @clone[$c][$c]; 544 | @clone[$c] = @clone[$c] >>/>> @clone[$c][$c]; 545 | for $c + 1 ..^ $!row-count -> $r { 546 | @inverted[$r] = @inverted[$r] >>-<< @clone[$r][$c] <<*<< @inverted[$c]; 547 | @clone[$r] = @clone[$r] >>-<< @clone[$r][$c] <<*<< @clone[$c]; 548 | } 549 | } 550 | for reverse(1 ..^ $!column-count) -> $c { 551 | for ^$c -> $r { 552 | @inverted[$r] = @inverted[$r] >>-<< @clone[$r][$c] <<*<< @inverted[$c]; 553 | @clone[$r] = @clone[$r] >>-<< @clone[$r][$c] <<*<< @clone[$c]; 554 | } 555 | } 556 | Math::Matrix.new( @inverted ); 557 | } 558 | 559 | method rref( Math::Matrix:D: --> Math::Matrix:D) { self.reduced-row-echelon-form } 560 | method reduced-row-echelon-form(Math::Matrix:D: --> Math::Matrix:D) { 561 | my @ref = self!clone-cells(); 562 | my $lead = 0; 563 | MAIN: for ^$!row-count -> $r { 564 | last MAIN if $lead >= $!column-count; 565 | my $i = $r; 566 | while @ref[$i][$lead] == 0 { 567 | $i++; 568 | if $!row-count == $i { 569 | $i = $r; 570 | $lead++; 571 | last MAIN if $lead == $!column-count; 572 | } 573 | } 574 | @ref[$i, $r] = @ref[$r, $i]; 575 | my $lead_value = @ref[$r][$lead]; 576 | @ref[$r] »/=» $lead_value; 577 | for ^$!row-count -> $n { 578 | next if $n == $r; 579 | @ref[$n] »-=» @ref[$r] »*» @ref[$n][$lead]; 580 | } 581 | $lead++; 582 | } 583 | return Math::Matrix.new( @ref ); 584 | } 585 | 586 | ################################################################################ 587 | # end of derivative matrices - start decompositions 588 | ################################################################################ 589 | 590 | # LU factorization with optional partial pivoting and optional diagonal matrix 591 | method LU-decomposition(Math::Matrix:D: Bool :$pivot = False, Bool :$diagonal = False, Bool :$Crout = False) { 592 | fail "Not an square matrix" unless self.is-square; 593 | fail "Has to be invertible when not using pivoting" if not $pivot and not self.is-invertible; 594 | fail "Crout algorithm makes only sense when not decomposing into a diagonal matrix" if $Crout and $diagonal; 595 | 596 | my $size = $!row-count; 597 | my @L = Math::Matrix::ArrayOfArray::new-identity( $size ); 598 | my Array @U = self!clone-cells( ); 599 | my @P = Math::Matrix::ArrayOfArray::new-identity( $size ); 600 | 601 | for 0 .. $size-2 -> $c { 602 | if $pivot { 603 | my $maxrow = $c; 604 | for $c+1 ..^$size -> $r { $maxrow = $c if @U[$maxrow][$c] < @U[$r][$c] } 605 | (@U[$maxrow], @U[$c]) = (@U[$c], @U[$maxrow]); 606 | (@P[$maxrow], @P[$c]) = (@P[$c], @P[$maxrow]); 607 | } 608 | 609 | for $c+1 ..^$size -> $r { 610 | next if @U[$r][$c] == 0; 611 | my $q = @L[$r][$c] = @U[$r][$c] / @U[$c][$c]; 612 | @U[$r;*] = @U[$r] >>-<< $q <<*<< @U[$c]; 613 | } 614 | } 615 | #@U = self!AoA-clone( @U ); 616 | 617 | if $diagonal { 618 | my @D; 619 | for 0 ..^ $size -> $c { 620 | push @D, @U[$c][$c]; 621 | @U[$c][$c] = 1; 622 | } 623 | $pivot ?? (Math::Matrix.new-lower-triangular(@L), Math::Matrix.new-diagonal(@D), 624 | Math::Matrix.new-upper-triangular(@U), Math::Matrix.new(@P)) 625 | !! (Math::Matrix.new-lower-triangular(@L), Math::Matrix.new-diagonal(@D), 626 | Math::Matrix.new-upper-triangular(@U)); 627 | } elsif $Crout { 628 | for 0 ..^ $size -> $i { 629 | next if @U[$i][$i] == 1; 630 | fail "Matrix can not be inverted" if @U[$i][$i] == 0; 631 | my $f = @U[$i][$i]; 632 | for $i ..^ $size -> $j { 633 | @L[$j][$i] *= $f; 634 | @U[$i][$j] /= $f; 635 | } 636 | } 637 | } 638 | $pivot ?? (Math::Matrix.new-lower-triangular(@L), Math::Matrix.new-upper-triangular(@U), Math::Matrix.new(@P)) 639 | !! (Math::Matrix.new-lower-triangular(@L), Math::Matrix.new-upper-triangular(@U)); 640 | } 641 | 642 | method Cholesky-decomposition(Math::Matrix:D: Bool :$diagonal = False) { 643 | fail "Matrix is not symmetric" unless self.is-symmetric; 644 | fail "Matrix is not positive definite" unless self.is-positive-definite; 645 | my @L = self!clone-cells(); 646 | for 0 ..^$!row-count -> $k { 647 | @L[$k][$k] -= @L[$k][$_]**2 for 0 .. $k-1; 648 | @L[$k][$k] = @L[$k][$k].sqrt; 649 | for $k+1 ..^ $!row-count -> $i { 650 | @L[$i][$k] -= @L[$i][$_] * @L[$k][$_] for 0 ..^ $k ; 651 | @L[$i][$k] /= @L[$k][$k]; 652 | } 653 | } 654 | for ^$!row-count X ^$!column-count -> ($r, $c) { @L[$r][$c] = 0 if $r < $c } 655 | return Math::Matrix.new-lower-triangular( @L ) unless $diagonal; 656 | my @D; 657 | for ^$!row-count -> $r { 658 | @D[$r] = @L[$r][$r] ** 2 ; 659 | @L[$r][$r] = 1; 660 | } 661 | Math::Matrix.new-lower-triangular( @L ), Math::Matrix.new-diagonal( @D ); 662 | } 663 | 664 | ################################################################################ 665 | # end of decompositions - start matrix math operations 666 | ################################################################################ 667 | 668 | 669 | method equal(Math::Matrix:D: Math::Matrix $b --> Bool) { @!rows ~~ $b!rows } 670 | multi method ACCEPTS(Math::Matrix:D: Math::Matrix:D $b --> Bool) { self.equal( $b ) } 671 | 672 | # add matrix 673 | multi method add(Math::Matrix:D: Str $b --> Math::Matrix:D ) { self.add( Math::Matrix.new( $b ) ) } 674 | multi method add(Math::Matrix:D: @b --> Math::Matrix:D ) { self.add( Math::Matrix.new( @b ) ) } 675 | multi method add(Math::Matrix:D: Math::Matrix $b where {self.size eqv $b.size} --> Math::Matrix:D ) { 676 | my @sum; 677 | for ^$!row-count X ^$!column-count -> ($r, $c) { 678 | @sum[$r][$c] = @!rows[$r][$c] + $b!rows[$r][$c]; 679 | } 680 | Math::Matrix.new( @sum ); 681 | } 682 | # add vector 683 | multi method add(Math::Matrix:D: @v where {@v.all ~~ Numeric}, Int :$row! --> Math::Matrix:D) { 684 | fail "Matrix has $!column-count columns, but got "~ +@v ~ "element row." unless $!column-count == +@v; 685 | self!check-row-index($row); 686 | my @m = self!clone-cells; 687 | @m[$row] = @m[$row] <<+>> @v; 688 | Math::Matrix.new( @m ); 689 | } 690 | multi method add(Math::Matrix:D: @v where {@v.all ~~ Numeric}, Int :$column! --> Math::Matrix:D ) { 691 | self!check-column-index($column); 692 | fail "Matrix has $!row-count rows, but got "~ +@v ~ "element column." unless $!row-count == +@v; 693 | my @m = self!clone-cells; 694 | @v.keys.map:{ @m[$_][$column] += @v[$_] }; 695 | Math::Matrix.new( @m ); 696 | } 697 | # add scalar 698 | multi method add(Math::Matrix:D: Numeric $s, --> Math::Matrix:D ) { self.map( * + $s ) } 699 | multi method add(Math::Matrix:D: Numeric $s, Int :$row, Int :$column --> Math::Matrix:D ) { 700 | self!check-row-index($row) if $row.defined; 701 | self!check-column-index($column) if $column.defined; 702 | my @sum = self!clone-cells; 703 | for ($row.defined ?? ($row..$row) !! ^$!row-count) 704 | X ($column.defined ?? ($column..$column) !! ^$!column-count) 705 | -> ($r, $c) { @sum[$r][$c] += $s } 706 | Math::Matrix.new( @sum ); 707 | } 708 | 709 | # multiply matrix 710 | multi method multiply(Math::Matrix:D: Str $b --> Math::Matrix:D ) { self.multiply( Math::Matrix.new( $b ) ) } 711 | multi method multiply(Math::Matrix:D: @b --> Math::Matrix:D ) { self.multiply( Math::Matrix.new( @b ) ) } 712 | multi method multiply(Math::Matrix:D: Math::Matrix $b where {self.size eqv $b.size} --> Math::Matrix:D ) { 713 | my @product; 714 | for ^$!row-count X ^$!column-count -> ($r, $c) { @product[$r][$c] = @!rows[$r][$c] * $b!rows[$r][$c] } 715 | Math::Matrix.new( @product ); 716 | } 717 | # multiply vector 718 | multi method multiply(Math::Matrix:D: @v where {@v.all ~~ Numeric}, Int :$row! --> Math::Matrix:D) { 719 | fail "Matrix has $!column-count columns, but got "~ +@v ~ "element row." unless $!column-count == +@v; 720 | self!check-row-index($row); 721 | my @m = self!clone-cells; 722 | @m[$row] = @m[$row] <<*>> @v; 723 | Math::Matrix.new( @m ); 724 | } 725 | multi method multiply(Math::Matrix:D: @v where {@v.all ~~ Numeric}, Int :$column! --> Math::Matrix:D ) { 726 | self!check-column-index($column); 727 | fail "Matrix has $!row-count rows, but got "~ +@v ~ "element column." unless $!row-count == +@v; 728 | my @m = self!clone-cells; 729 | @v.keys.map:{ @m[$_][$column] *= @v[$_] }; 730 | Math::Matrix.new( @m ); 731 | } 732 | # multiply scalar 733 | multi method multiply(Math::Matrix:D: Numeric $f --> Math::Matrix:D ) { self.map( * * $f ) } 734 | multi method multiply(Math::Matrix:D: Numeric $f, Int :$row, Int :$column --> Math::Matrix:D ) { 735 | self!check-row-index($row) if $row.defined; 736 | self!check-column-index($column) if $column.defined; 737 | my @product = self!clone-cells; 738 | for ($row.defined ?? ($row..$row) !! ^$!row-count) 739 | X ($column.defined ?? ($column..$column) !! ^$!column-count) 740 | -> ($r, $c) { @product[$r][$c] *= $f } 741 | Math::Matrix.new( @product ); 742 | } 743 | 744 | method dot-product(Math::Matrix:D: Math::Matrix $b --> Math::Matrix:D ) { 745 | fail "Number of columns of the second matrix is different from number of rows of the first operand" 746 | unless $!column-count == $b!row-count; 747 | my @product; 748 | for ^$!row-count X ^$b!column-count -> ($r, $c) { 749 | @product[$r][$c] += @!rows[$r][$_] * $b!rows[$_][$c] for ^$b!row-count; 750 | } 751 | Math::Matrix.new( @product ); 752 | } 753 | 754 | method tensor-product(Math::Matrix:D: Math::Matrix $b --> Math::Matrix:D) { 755 | my @product; 756 | for @!rows -> $arow { 757 | for $b!rows -> $brow { 758 | @product.push([ ($arow.list.map: { $brow.flat >>*>> $_ }).flat ]); 759 | } 760 | } 761 | Math::Matrix.new( @product ); 762 | } 763 | 764 | ################################################################################ 765 | # end of matrix math operations - start list like operations 766 | ################################################################################ 767 | 768 | method elems (Math::Matrix:D: --> Int) { $!row-count * $!column-count } 769 | 770 | method elem (Math::Matrix:D: Range $r --> Bool) { # is every element value element in the set/range 771 | self.list.map: {return False unless $_ ~~ $r}; 772 | True; 773 | } 774 | 775 | multi method cont (Math::Matrix:D: Numeric $e --> Bool) { # matrix contains element ? 776 | self.list.map: {return True if $_ == $e}; 777 | False; 778 | } 779 | multi method cont (Math::Matrix:D: Range $r --> Bool) { # is any element value in this set/range 780 | self.list.map: {return True if $_ ~~ $r}; 781 | False; 782 | } 783 | 784 | method map(Math::Matrix:D: &coderef, Range :$rows = ^$!row-count, 785 | Range :$columns = ^$!column-count --> Math::Matrix:D) { 786 | self!check-index($rows.min, $columns.min); 787 | self!check-row-index($rows.minmax[1]) unless $rows.max == Inf; 788 | self!check-column-index($columns.minmax[1]) unless $columns.max == Inf; 789 | my @r = $rows.max == Inf ?? ($rows.min .. $!row-count-1).list !! $rows.list; 790 | my @c = $columns.max == Inf ?? ($columns.min .. $!column-count-1).list !! $columns.list; 791 | my @m; 792 | for @r X @c -> ($r, $c) { @m[$r][$c] = &coderef(@!rows[$r][$c])} 793 | for ^$!row-count X ^$!column-count -> ($r, $c) { @m[$r][$c] //= @!rows[$r][$c] } 794 | Math::Matrix.new( @m ); 795 | } 796 | 797 | method map-with-index(Math::Matrix:D: &coderef, Range :$rows = ^$!row-count, 798 | Range :$columns = ^$!column-count --> Math::Matrix:D) { 799 | fail "block has to receive between one and three arguments" unless &coderef.arity ~~ 1..3; 800 | self!check-index($rows.min, $columns.min); 801 | self!check-row-index($rows.minmax[1]) unless $rows.max == Inf; 802 | self!check-column-index($columns.minmax[1]) unless $columns.max == Inf; 803 | my @r = $rows.max == Inf ?? ($rows.min .. $!row-count-1).list !! $rows.list; 804 | my @c = $columns.max == Inf ?? ($columns.min .. $!column-count-1).list !! $columns.list; 805 | my @m; 806 | if &coderef.arity == 1 {for @r X @c -> ($r, $c) { @m[$r][$c] = &coderef($r) }} 807 | elsif &coderef.arity == 2 {for @r X @c -> ($r, $c) { @m[$r][$c] = &coderef($r, $c) }} 808 | elsif &coderef.arity == 3 {for @r X @c -> ($r, $c) { @m[$r][$c] = &coderef($r, $c, @!rows[$r][$c]) }} 809 | for ^$!row-count X ^$!column-count -> ($r, $c) { @m[$r][$c] //= @!rows[$r][$c] } 810 | Math::Matrix.new( @m ); 811 | } 812 | 813 | 814 | method reduce( Math::Matrix:D: &coderef) {(@!rows.map: {$_.flat}).flat.reduce( &coderef )} 815 | method reduce-rows (Math::Matrix:D: &coderef) { @!rows.map: { $_.flat.reduce( &coderef) }} 816 | method reduce-columns(Math::Matrix:D: &coderef) {(^$!column-count).map: { self.column($_).reduce( &coderef )}} 817 | 818 | ################################################################################ 819 | # end of list like operations - start structural matrix operations 820 | ################################################################################ 821 | 822 | multi method move-row (Math::Matrix:D: Pair $p --> Math::Matrix:D) { 823 | self.move-row($p.key, $p.value) 824 | } 825 | multi method move-row (Math::Matrix:D: Int $from, Int $to --> Math::Matrix:D) { 826 | self!check-row-indices([$from, $to]); 827 | return self if $from == $to; 828 | my @rows = (^$!row-count).list; 829 | @rows.splice($to,0,@rows.splice($from,1)); 830 | self.submatrix( rows => @rows, columns => (^$!column-count).list); 831 | } 832 | 833 | multi method move-column (Math::Matrix:D: Pair $p --> Math::Matrix:D) { 834 | self.move-column($p.key, $p.value) 835 | } 836 | multi method move-column (Math::Matrix:D: Int $from, Int $to --> Math::Matrix:D) { 837 | self!check-column-indices([$from, $to]); 838 | return self if $from == $to; 839 | my @cols = (^$!column-count).list; 840 | @cols.splice($to,0,@cols.splice($from,1)); 841 | self.submatrix( rows => (^$!row-count).list, columns => @cols); 842 | } 843 | 844 | method swap-rows (Math::Matrix:D: Int $rowa, Int $rowb --> Math::Matrix:D) { 845 | self!check-row-indices([$rowa, $rowb]); 846 | return self if $rowa == $rowb; 847 | my @rows = (^$!row-count).list; 848 | (@rows.[$rowa], @rows.[$rowb]) = (@rows.[$rowb], @rows.[$rowa]); 849 | self.submatrix( rows => @rows, columns => (^$!column-count).list); 850 | } 851 | 852 | method swap-columns (Math::Matrix:D: Int $cola, Int $colb --> Math::Matrix:D) { 853 | self!check-column-indices([$cola, $colb]); 854 | return self if $cola == $colb; 855 | my @cols = (^$!column-count).list; 856 | (@cols.[$cola], @cols.[$colb]) = (@cols.[$colb], @cols.[$cola]); 857 | self.submatrix( rows => (^$!row-count).list, columns => @cols); 858 | } 859 | 860 | multi method splice-rows(Math::Matrix:D: Int $row, Int $elems, Math::Matrix $replacement --> Math::Matrix:D){ 861 | self.splice-rows($row, $elems, $replacement.Array ); 862 | } 863 | multi method splice-rows(Math::Matrix:D: Int $row, Int $elems = ($!row-count - $row), Array $replacement = [] --> Math::Matrix:D){ 864 | my $pos = $row >= 0 ?? $row !! $!row-count + $row + 1; 865 | fail "Row index (first parameter) is outside of matrix size!" unless 0 <= $pos <= $!row-count; 866 | fail "Number of elements to delete (second parameter) has to be zero or more!)" if $elems < 0; 867 | if $replacement.elems > 0 { 868 | fail "Number of columns in and original matrix and replacement has to be same" unless $replacement[0].elems == $!column-count; 869 | Math::Matrix::ArrayOfArray::check-data( @$replacement ); 870 | } 871 | my @m = self!clone-cells; 872 | @m.splice($pos, $elems, $replacement.list); 873 | Math::Matrix.new(@m); 874 | } 875 | 876 | 877 | multi method splice-columns(Math::Matrix:D: Int $col, Int $elems, Math::Matrix $replacement --> Math::Matrix:D){ 878 | self.splice-columns($col, $elems, $replacement.Array ); 879 | } 880 | multi method splice-columns(Math::Matrix:D: Int $col, Int $elems = ($!column-count - $col), Array $replacement = ([[] xx $!row-count]) --> Math::Matrix:D){ 881 | my $pos = $col >= 0 ?? $col !! $!column-count + $col + 1; 882 | fail "Column index (first parameter) is outside of matrix size!" unless 0 <= $pos <= $!column-count; 883 | fail "Number of elements to delete (second parameter) has to be zero or more!)" if $elems < 0; 884 | fail "Number of rows in original matrix and replacement has to be same" unless $replacement.elems == $!row-count; 885 | Math::Matrix::ArrayOfArray::check-data( @$replacement ); 886 | my @m = self!clone-cells; 887 | @m.keys.map:{ @m[$_].splice($pos, $elems, $replacement[$_]) }; 888 | Math::Matrix.new(@m); 889 | } 890 | 891 | ################################################################################ 892 | # end of structural matrix operations - start operators 893 | ################################################################################ 894 | 895 | multi sub prefix:<@>( Math::Matrix:D $m --> Array) is export { $m.Array } 896 | multi sub prefix:<%>( Math::Matrix:D $m --> Hash) is export { $m.Hash } 897 | multi sub prefix:<->( Math::Matrix:D $m --> Math::Matrix:D) is export { $m.negated } 898 | 899 | multi sub circumfix:<| |>( Math::Matrix:D $m --> Numeric) is equiv(&prefix:) is export { $m.determinant } 900 | multi sub circumfix:<‖ ‖>( Math::Matrix:D $m --> Numeric) is equiv(&prefix:) is export { $m.norm } 901 | 902 | 903 | multi sub infix:<+>( Math::Matrix:D $a, Numeric $n --> Math::Matrix:D) is export { $a.add($n) } 904 | multi sub infix:<+>( Numeric $n, Math::Matrix:D $a --> Math::Matrix:D) is export { $a.add($n) } 905 | multi sub infix:<+>( Math::Matrix:D $a, Math::Matrix:D $b --> Math::Matrix:D) is export { $a.add($b) } 906 | 907 | multi sub infix:<->( Numeric $n, Math::Matrix:D $a --> Math::Matrix:D) is export { $a.negated.add($n) } 908 | multi sub infix:<->( Math::Matrix:D $a, Numeric $n --> Math::Matrix:D) is export { $a.add(-$n) } 909 | multi sub infix:<->( Math::Matrix:D $a, Math::Matrix:D $b --> Math::Matrix:D) is export { $a.subtract($b) } 910 | 911 | multi sub infix:<*>( Math::Matrix:D $a, Numeric $n --> Math::Matrix:D) is export { $a.multiply($n) } 912 | multi sub infix:<*>( Numeric $n, Math::Matrix:D $a --> Math::Matrix:D) is export { $a.multiply($n) } 913 | multi sub infix:<*>( Math::Matrix:D $a, Math::Matrix:D $b --> Math::Matrix:D) is export { $a.multiply($b) } 914 | multi sub infix:<**>( Math::Matrix:D $a where { $a.is-square }, Int $e --> Math::Matrix:D) is export { 915 | return Math::Matrix.new-identity( $a!row-count ) if $e == 0; 916 | my $p = $a.clone; 917 | $p = $p.dot-product( $a ) for 2 .. abs $e; 918 | $p = $p.inverted if $e < 0; 919 | $p; 920 | } 921 | 922 | multi sub infix:<⋅>( Math::Matrix:D $a, Math::Matrix:D $b --> Math::Matrix:D) is tighter(&infix:<*>) is export { $a.dot-product( $b ) } 923 | multi sub infix:(Math::Matrix:D $a, Math::Matrix:D $b --> Math::Matrix:D) is equiv(&infix:<⋅>) is export { $a.dot-product( $b ) } 924 | multi sub infix:<÷>( Math::Matrix:D $a, Math::Matrix:D $b --> Math::Matrix:D) is equiv(&infix:<⋅>) is export { $a.dot-product( $b.inverted ) } 925 | 926 | multi sub infix:<⊗>( Math::Matrix:D $a, Math::Matrix:D $b --> Math::Matrix:D) is equiv(&infix:) is export { $a.tensor-product( $b ) } 927 | multi sub infix:( Math::Matrix:D $a, Math::Matrix:D $b --> Math::Matrix:D) is equiv(&infix:) is export { $a.tensor-product( $b ) } 928 | 929 | multi sub prefix:(Str $m --> Math::Matrix:D) is tighter(&postcircumfix:<[ ]>) is export(:MM) { Math::Matrix.new($m) } 930 | multi sub prefix:(List $m --> Math::Matrix:D) is tighter(&postcircumfix:<[ ]>) is export(:MM) { Math::Matrix.new(@$m) } 931 | -------------------------------------------------------------------------------- /lib/Math/Matrix/ArrayOfArray.pm6: -------------------------------------------------------------------------------- 1 | use v6.c; 2 | use Math::Matrix::Type; 3 | 4 | unit module Math::Matrix::ArrayOfArray; 5 | 6 | ################################################################################ 7 | # constructors 8 | ################################################################################ 9 | 10 | our sub new-uniform( PosInt $rows, PosInt $cols, Numeric $content ) { [ [ $content xx $cols ] xx $rows ] } 11 | our sub new-zero( PosInt $rows, PosInt $cols = $rows ) { new-uniform($rows, $cols, 0) } 12 | 13 | our sub new-identity( PosInt $size ) { 14 | my @identity = new-zero($size); 15 | @identity[$_][$_] = 1 for ^$size; 16 | @identity; 17 | } 18 | 19 | our sub new-diagonal( NumList $diag ) { 20 | my @diagonal = new-zero( $diag.elems ); 21 | for ^$diag.elems -> $i { @diagonal[$i][$i] = $diag[$i] } 22 | @diagonal; 23 | } 24 | 25 | 26 | ################################################################################ 27 | 28 | our sub clone (@m) {[ @m.map: {[ $^row.flat.map({$^element.clone}) ]} ]} 29 | 30 | ################################################################################ 31 | 32 | our sub check-data (@m) { 33 | fail "Expect an Array or Array or List of Lists" unless (@m ~~ Array and all @m ~~ Array) 34 | or (@m ~~ List and all @m ~~ List); 35 | fail "Expect the Array or List to have elements" if @m == 0 or @m[0] == 0; 36 | fail "All rows must contains the same number of elements" unless @m == 1 or @m[0] == all @m[*]; 37 | fail "All rows must contain only numeric values" unless all( @m[*;*] ) ~~ Numeric; 38 | } 39 | 40 | our sub check-size-equal(@a, @b){ 41 | fail "Expect same amount of rows in both operands, but got {@a.elems} and {@b.elems}" unless @a == @b; 42 | for ^@a.elems -> $row { 43 | fail "Expect same amount of elements in row $row in both operands, but got {@a[$row].elems} and {@b[$row].elems}" 44 | unless @a[$row] == @b[$row]; 45 | } 46 | } 47 | 48 | our sub check-size-conformable(@a, @b){ # for multiplication 49 | fail "All rows of first operand must contains the same number of elements" unless @a == 1 or @a[0] == all @a[*]; 50 | fail "All rows of second operand must contains the same number of elements" unless @b == 1 or @b[0] == all @b[*]; 51 | fail "Number of columns in left operand has to equal number of rows on right" unless @a[0] == @b; 52 | } 53 | 54 | our sub transpose(@m){ 55 | my @t = [Z] @m; 56 | @t.map: *.Array; 57 | } 58 | 59 | our sub add(@a, @b){ 60 | my @sum = clone(@a); 61 | for ^@a.elems -> $row { @sum[$row] = @sum[$row] <<+>> @b[$row] } 62 | @sum; 63 | } 64 | 65 | our sub multiply(@a, @b){ 66 | my @bt = transpose(@b); 67 | my @product; 68 | for ^@a.elems X ^@b.elems -> ($row, $col) { 69 | @product[$row][$col] = [+] (@a[$row] <<*>> @b[$col]); 70 | } 71 | @product; 72 | } 73 | 74 | our sub map (@m, &coderef){ 75 | my @res = clone(@m); 76 | for ^@m.elems -> $row { @res[$row] = @res[$row].map(&coderef) } 77 | @res; 78 | } 79 | -------------------------------------------------------------------------------- /lib/Math/Matrix/Type.pm6: -------------------------------------------------------------------------------- 1 | use v6.c; 2 | 3 | unit module Math::Matrix::Type; 4 | 5 | subset PosInt of Int is export where * > 0; 6 | subset NumList of List is export where { .all ~~ (Numeric & .defined) }; 7 | subset NumArray of Array is export where { .all ~~ (Numeric & .defined) }; 8 | -------------------------------------------------------------------------------- /lib/Math/Matrix/Util.pm6: -------------------------------------------------------------------------------- 1 | use v6.c; 2 | use Math::Matrix::Type; 3 | 4 | unit role Math::Matrix::Util; 5 | 6 | method !rows { ... } 7 | method !clone-rows { ... } 8 | method !row-count { ... } 9 | method !column-count { ... } 10 | 11 | ################################################################################ 12 | # checker 13 | ################################################################################ 14 | 15 | submethod !check-row-index (Int $row) { 16 | fail X::OutOfRange.new(:what, 17 | :got($row), 18 | :range(0 .. self!row-count - 1)) unless 0 <= $row < self!row-count 19 | } 20 | 21 | submethod !check-column-index (Int $col) { 22 | fail X::OutOfRange.new(:what, 23 | :got($col), 24 | :range(0 .. self!column-count - 1)) unless 0 <= $col < self!column-count 25 | } 26 | submethod !check-index (Int $row, Int $col) { 27 | self!check-row-index($row); 28 | self!check-column-index($col); 29 | } 30 | 31 | submethod !check-row-indices ( @row) { 32 | fail "Row index has to be an Int." unless all(@row) ~~ Int; 33 | fail X::OutOfRange.new( :what, 34 | :got(@row), 35 | :range("0..{self!row-count -1 }")) unless 0 <= all(@row) < self!row-count; 36 | } 37 | submethod !check-column-indices ( @col) { 38 | fail "Column index has to be an Int." unless all(@col) ~~ Int; 39 | fail X::OutOfRange.new( :what, 40 | :got(@col), 41 | :range("0..{self!column-count -1 }")) unless 0 <= all(@col) < self!column-count; 42 | } 43 | submethod !check-indices (@row, @col) { 44 | self!check-row-indices(@row); 45 | self!check-column-indices(@col); 46 | } 47 | 48 | ################################################################################ 49 | # helper 50 | ################################################################################ 51 | 52 | method cofactor-sign( Int:D $row, Int:D $col ) { (-1) ** (($row+$col) mod 2) } 53 | -------------------------------------------------------------------------------- /roadmap.txt: -------------------------------------------------------------------------------- 1 | more tests 2 | 3 | 4 | decompositions: 5 | 6 | -- eigenvalue 7 | -- singular value 8 | -------------------------------------------------------------------------------- /t/010-load.t: -------------------------------------------------------------------------------- 1 | use lib "lib"; 2 | use Test; 3 | 4 | plan 1; 5 | 6 | use-ok('Math::Matrix'); 7 | -------------------------------------------------------------------------------- /t/011-meta.t: -------------------------------------------------------------------------------- 1 | use v6; 2 | use lib 'lib'; 3 | use Test; 4 | plan 1; 5 | 6 | constant PERL6_TEST_META = ?%*ENV; 7 | 8 | if PERL6_TEST_META { 9 | require Test::META <&meta-ok>; 10 | meta-ok; 11 | done-testing; 12 | } 13 | else { 14 | skip-rest "Skipping meta test"; 15 | exit; 16 | } 17 | -------------------------------------------------------------------------------- /t/020-constructors.t: -------------------------------------------------------------------------------- 1 | use lib "lib"; 2 | use Test; 3 | use Math::Matrix; 4 | plan 32; 5 | 6 | dies-ok { my $matrix = Math::Matrix.new() } , "Constructor need params"; 7 | dies-ok { my $matrix = Math::Matrix.new( [] ) } , "Empty row Array is not enough"; 8 | dies-ok { my $matrix = Math::Matrix.new( [[],[]]) } , "Empty columns Arrays are not enough"; 9 | dies-ok { my $matrix = Math::Matrix.new( ()) } , "Empty row List is not enough"; 10 | dies-ok { my $matrix = Math::Matrix.new( ((),())) } , "Empty columns Lists are not enough"; 11 | dies-ok { my $matrix = Math::Matrix.new( "" ) } , "Empty String as Input is not enough"; 12 | dies-ok { my $matrix = Math::Matrix.new( "\n\n" ) } , "String with empty lines as Input is not enough"; 13 | dies-ok { my $matrix = Math::Matrix.new( [[1,2],[1,2,3]]) } , "Different nuber of elements per line"; 14 | dies-ok { my $matrix = Math::Matrix.new( [[1,2],[3,"a"]]) } , "All elements have to be Numeric"; 15 | lives-ok { my $matrix = Math::Matrix.new( [[1,2],[3,4]]) } , "Able to create a int matrix with Array of Array syntax"; 16 | lives-ok { my $matrix = Math::Matrix.new( [[.1,2.11111],[3/5,4e-2]]) }, "created a rational matrix"; 17 | lives-ok { my $matrix = Math::Matrix.new( [[1,2],[3,4+i]]) } , "Able to create a complex matrix"; 18 | lives-ok { my $matrix = Math::Matrix.new( [[True, False],[False,True]]) }, "created a Bool matrix"; 19 | lives-ok { my $matrix = Math::Matrix.new( ((1,2),(3,4))) } , "Able to create matrix with List of List syntax"; 20 | lives-ok { my $matrix = Math::Matrix.new( "1 2 \n 3 4") } , "Able to create a int matrix with Str syntax"; 21 | 22 | my $matrixa = Math::Matrix.new([[1,2],[3,4]]); 23 | ok $matrixa ~~ Math::Matrix , "object was created of right type"; 24 | dies-ok { my $matrixa.new([[1,2],[1,2,3]]); }, "can not call new on existing matrix"; 25 | ok $matrixa ~~ Math::Matrix.new(((1,2),(3,4))), "AoA and LoL syntax work the same"; 26 | ok $matrixa ~~ Math::Matrix.new("1 2 \n 3 4") , "AoA and Str syntax work the same"; 27 | 28 | 29 | my $data = [[1,3],[3,25]]; 30 | my $samedata = [[1,3],[3,25]]; 31 | my $dataMatrix = Math::Matrix.new($data);; 32 | my $samedataMatrix = Math::Matrix.new($samedata);; 33 | $data[0][0] = 0; 34 | ok $dataMatrix ~~ $samedataMatrix, "no bleed from input data to matrix"; 35 | 36 | 37 | my $matrixb = Math::Matrix.new([[1,2],[3,4]]); 38 | my $matrixc = Math::Matrix.new([[8,8],[8,8]]); 39 | my $matrixd = Math::Matrix.new([[ 1.0, 2.0 ],[ 3.0 , 4.0 ]]); 40 | 41 | ok $matrixa.equal( $matrixb ), "equal method working"; 42 | ok $matrixa ~~ $matrixb , "~~ operator working"; 43 | 44 | nok $matrixa.equal( $matrixc), "Non equal matrices, with equal method"; 45 | nok $matrixa ~~ $matrixc , "Non equal matrices, foud via ~~"; 46 | 47 | ok $matrixa.equal( $matrixd) , "equal method working with real values"; 48 | ok $matrixa ~~ $matrixd , "~~ operator working with real values"; 49 | 50 | my $zero = Math::Matrix.new-zero(3,4); 51 | my $expectz = Math::Matrix.new([[0,0,0,0],[0,0,0,0],[0,0,0,0]]); 52 | ok $zero ~~ $expectz, "Get zero matrix"; 53 | is ?$zero, False , "zero matrix is false in bool context by prefix op"; 54 | 55 | my $identity = Math::Matrix.new-identity(3); 56 | my $expected = Math::Matrix.new([[1,0,0],[0,1,0],[0,0,1]]); 57 | ok $identity ~~ $expected, "Get identity matrix"; 58 | 59 | my $diagonal = Math::Matrix.new-diagonal([1,2,3]); 60 | my $expectd = Math::Matrix.new([[1,0,0],[0,2,0],[0,0,3]]); 61 | ok $diagonal ~~ $expectd, "Get diagonal matrix"; 62 | 63 | #TODO: reinstate test either in success or failure 64 | my $diagonal2 = Math::Matrix.new-diagonal( 1, 2, 3 ); 65 | ok $diagonal2 ~~ $expectd, "Get diagonal matrix"; 66 | 67 | my $product = Math::Matrix.new-vector-product([1,2,3],[2,3,4]); 68 | my $pexpect = Math::Matrix.new([[2,3,4],[4,6,8],[6,9,12]]); 69 | ok $product.equal( $pexpect ), "matrix construction by vector product"; 70 | -------------------------------------------------------------------------------- /t/021-accessors.t: -------------------------------------------------------------------------------- 1 | use lib "lib"; 2 | use Test; 3 | use Math::Matrix; 4 | plan 5; 5 | 6 | 7 | subtest { 8 | plan 4; 9 | my $matrix = Math::Matrix.new([[4,0,1],[2,1,0],[2,2,3]]); 10 | 11 | ok $matrix.element(0,0) == 4, "first element"; 12 | ok $matrix.element(2,1) == 2, "first element"; 13 | dies-ok { my $element = $matrix.element(5,0); }, "Out of range row"; 14 | dies-ok { my $element = $matrix.element(0,5); }, "Out of range column"; 15 | 16 | }, "Element"; 17 | 18 | 19 | subtest { 20 | plan 2; 21 | my $matrix = Math::Matrix.new([[4,0,1],[2,1,0],[2,2,3]]); 22 | 23 | ok $matrix.row(1) == (2,1,0), "got second row"; 24 | dies-ok { $matrix.row(5) }, "tried none existing row"; 25 | }, "Row"; 26 | 27 | 28 | subtest { 29 | plan 2; 30 | my $matrix = Math::Matrix.new([[4,0,1],[2,1,0],[2,2,3]]); 31 | 32 | ok $matrix.column(1) == (0,1,2), "got second column"; 33 | dies-ok { $matrix.column(5) }, "tried none existing column"; 34 | }, "Column"; 35 | 36 | 37 | subtest { 38 | plan 18; 39 | my $matrix = Math::Matrix.new([[4,0,1],[2,1,0],[2,2,3]]); 40 | my $identity = Math::Matrix.new-identity(3); 41 | my $morerows = Math::Matrix.new([[1,2],[3,4],[5,6]]); 42 | my $morecols = Math::Matrix.new([[6,7,8],[10,11,12]]); 43 | 44 | ok $matrix.diagonal() ~~ $matrix.diagonal(0),"main diagonal is default"; 45 | ok $matrix.diagonal() ~~ (4,1,3), "custom diagonal"; 46 | ok $matrix.diagonal(-1) ~~ (0,0), "short custom diagonal"; 47 | ok $identity.diagonal() ~~ (1,1,1), "identity diagonal"; 48 | ok ($identity.diagonal(2) ~~ (0,)), "short identity diagonal"; 49 | 50 | ok $morerows.diagonal(0) ~~ (1,4), "main diagonal of matrix with more rows"; 51 | ok $morerows.diagonal(1) == (3,6), "low diagonal of matrix with more rows"; 52 | ok $morerows.diagonal(-1) ~~ (2,), "high diagonal of matrix with more rows"; 53 | ok $morecols.diagonal(0) ~~ (6,11), "main diagonal of matrix with more cols"; 54 | ok $morecols.diagonal(1) ~~ (10,), "low diagonal of matrix with more cols"; 55 | ok $morecols.diagonal(-1) ~~ (7,12), "high diagonal of matrix with more cols"; 56 | 57 | ok $identity.skew-diagonal() == (0,1,0),"main skew diagonal of identity matrix"; 58 | ok $matrix.skew-diagonal(-1) == (2,0), "upper skew diagonal"; 59 | ok $matrix.skew-diagonal(2) == (3,), "lower skew diagonal"; 60 | 61 | dies-ok { Math::Matrix.new([[2,2,3]]).diagonal(1); }, "tried get diagonal outside of bound"; 62 | dies-ok { $identity.diagonal(-3); }, "tried get diagonal of identity outside of bound"; 63 | dies-ok { Math::Matrix.new([[2,2,3]]).skew-diagonal(); }, "get skew diag only for square matrices"; 64 | dies-ok { $identity.skew-diagonal(-3); }, "tried get skew diagonal outside of bound"; 65 | }, "Diagonal"; 66 | 67 | 68 | subtest { 69 | plan 16; 70 | my $matrix = Math::Matrix.new([[1,2,3,4],[5,6,7,8],[9,10,11,12]]); 71 | my $fsmatrix = Math::Matrix.new([[6,7,8],[10,11,12]]); 72 | my $lsmatrix = Math::Matrix.new([[1,2,3],[5,6,7]]); 73 | my $rehashed = Math::Matrix.new([[11,9,12],[3,1,4]]); 74 | my $dropfrow = Math::Matrix.new([[5,6,7,8],[9,10,11,12]]); 75 | my $dropfcol = Math::Matrix.new([[2,3,4],[6,7,8],[10,11,12]]); 76 | 77 | dies-ok { $matrix.submatrix(10,1); }, "demanded rows are out of range"; 78 | dies-ok { $matrix.submatrix(1,5); }, "demanded colums are out of range"; 79 | dies-ok { $matrix.submatrix( rows => -1..7, columns => 2..8) }, "demanded submatrix goes out of scope due second element"; 80 | dies-ok { $matrix.submatrix( rows => 1.1 ..2, columns => 2.. 3) }, "reject none int indices"; 81 | dies-ok { $matrix.submatrix( rows => (2..4), columns => (1..5)) }, "rows and colums are out of range"; 82 | 83 | ok $matrix.submatrix(0,0) ~~ $fsmatrix, "submatrix built by removing first element"; 84 | ok $matrix.submatrix(2,3) ~~ $lsmatrix, "submatrix built by removing last element"; 85 | ok $matrix.submatrix( rows => 1..2, columns => 1 .. 3) ~~ $fsmatrix, "submatrix with range syntax"; 86 | ok $matrix.submatrix( rows => 1..2, columns => 1 .. *) ~~ $fsmatrix, "submatrix with range syntax using * aka Inf"; 87 | ok $matrix.submatrix( rows => (1,2),columns => (1...3))~~ $fsmatrix, "simple submatrix created with list syntax"; 88 | ok $matrix.submatrix( rows => (2,0),columns => (2,0,3))~~ $rehashed, "rehashed submatrix using list syntax"; 89 | ok $matrix.submatrix( ) ~~ $matrix , "submatrix with no arguments is matrix itself"; 90 | 91 | ok $matrix.submatrix( rows => 1..*) ~~ $dropfrow, "submatrix with range syntax omiting column parameter"; 92 | ok $matrix.submatrix( columns => 1..3) ~~ $dropfcol, "submatrix with range syntax omiting row parameter"; 93 | ok $matrix.submatrix( rows => (1,2)) ~~ $dropfrow, "submatrix with list syntax omiting column parameter"; 94 | ok $matrix.submatrix( columns => (1,2,3)) ~~ $dropfcol, "submatrix with list syntax omiting row parameter"; 95 | 96 | }, "Submatrix"; 97 | 98 | -------------------------------------------------------------------------------- /t/022-converter.t: -------------------------------------------------------------------------------- 1 | use lib "lib"; 2 | use Test; 3 | use Math::Matrix; 4 | plan 23; 5 | 6 | my $matrixi = Math::Matrix.new([[1,2],[3,4]]); 7 | my $matrixr = Math::Matrix.new([[ 1.1, 2.2 ],[ 3.3 , 4.4 ]]); 8 | my $matrixc = Math::Matrix.new([[ 1.1+i, 2.2-i ],[ 3.3+2i , 4.4-3.4i ]]); 9 | 10 | 11 | ok $matrixi == $matrixi , ".equal method works"; 12 | my $from-perl = EVAL($matrixi.perl); 13 | ok $from-perl ~~ $matrixi , ".perl result can be evaled in a similar object"; 14 | 15 | ok $matrixi.Str().WHAT ~~ Str , "Method Str should return a String"; 16 | is $matrixi.Str(), "1 2\n3 4" , "value is correct in Str context "; 17 | is ~$matrixi, "1 2\n3 4" , "content is correct in string context by prefix op"; 18 | ok $matrixi.Numeric ~~ Numeric , "method .Numeric returns right type"; 19 | is +$matrixi, sqrt(30) , "content is correct in numeric context by prefix op"; 20 | ok $matrixi.Bool ~~ Bool , "method .Bool returns right type"; 21 | is ?$matrixi, True , "content is correct in bool context by prefix op"; 22 | ok $matrixi.list ~~ List , "method .List returns correct type"; 23 | ok $matrixi.list ~~ (1,2,3,4) , "method .List returns correct content"; 24 | ok |$matrixi == (1,2,3,4) , "correct list context conversion with prefix op"; 25 | ok $matrixi.Hash ~~ Hash , "method .Hash return correct type"; 26 | ok %$matrixi == { 0=> {0=>1, 1=>2}, 1=> {0=>3, 1=>4}}, "correct hash context conversion with prefix op"; 27 | 28 | is ~$matrixr, "1.1 2.2\n3.3 4.4" , "correct content of real values in string context"; 29 | is ~$matrixc, "1.1+1i 2.2-1i\n3.3+2i 4.4-3.4i", "correct content of complex values in Str context"; 30 | 31 | ok $matrixi.list == (1, 2, 3, 4) , "list context"; 32 | ok $matrixi.list-rows == ((1, 2), (3, 4)), "vertical list of lists context"; 33 | ok $matrixi.list-columns == ((1,3),(2,4)), "horizontal list of lists context"; 34 | ok $matrixi.Range == 1..4, "Range context"; 35 | 36 | ok $matrixi.Array == [[1, 2], [3, 4]] , "Array context"; 37 | ok $matrixi.Hash == { 0=>{0=>1,1=>2}, 1=>{0=>3, 1=>4}} , "Hash context"; 38 | 39 | ok Math::Matrix.gist eq "(Math::Matrix)" , "gist of type object"; 40 | -------------------------------------------------------------------------------- /t/030-property-bool.t: -------------------------------------------------------------------------------- 1 | use lib "lib"; 2 | use Test; 3 | use Math::Matrix; 4 | plan 100; 5 | 6 | my $matrixa = Math::Matrix.new([[1,2],[3,4]]); 7 | my $matrixc = Math::Matrix.new([[8,8],[8,8]]); 8 | my $matrixd = Math::Matrix.new([[1,2,3],[4,5,6]]); 9 | 10 | my $matrixh = Math::Matrix.new([[1,2+i],[2-i,4]]); 11 | my $matrixu = Math::Matrix.new([[0,i],[i,0]]); 12 | 13 | my $zero = Math::Matrix.new-zero(3,4); 14 | my $z3 = Math::Matrix.new-zero(3); 15 | my $identity = Math::Matrix.new-identity(3); 16 | my $diagonal = Math::Matrix.new-diagonal(1,2,3); 17 | my $ut = Math::Matrix.new([[1,2,3],[0,5,6],[0,0,6]]); 18 | my $lt = Math::Matrix.new([[1,0,0],[4,5,0],[4,5,6]]); 19 | my $sut = Math::Matrix.new([[0,1],[0,0]]); # strictly upper triangular matrix 20 | my $slt = Math::Matrix.new([[0,0],[1,0]]); # strictly lower triangular matrix 21 | my $uut = Math::Matrix.new([[1,2,3],[0,1,6],[0,0,1]]); 22 | my $lut = Math::Matrix.new([[1,0,0],[4,1,0],[4,5,1]]); 23 | 24 | 25 | my $symmetric = Math::Matrix.new([[ 1, 2, 3, 4 ], 26 | [ 2, 1, 5, 6 ], 27 | [ 3, 5, 1, 7 ], 28 | [ 4, 6, 7, 1 ]]); 29 | 30 | my $almostidentity = Math::Matrix.new([ [ 1, 0, 0 ], 31 | [ 0, 1, 0 ] ]); 32 | 33 | my $frobenius = Math::Matrix.new([[ 1, 0, 0, 0 ], 34 | [ 0, 1, 0, 0 ], 35 | [ 0, 5, 1, 0 ], 36 | [ 0, 6, 0, 1 ]]); 37 | 38 | my $tridiag = Math::Matrix.new([[ 2, 3, 0, 0 ], 39 | [ 1, 2, 3, 0 ], 40 | [ 0, 1, 2, 3 ], 41 | [ 0, 0, 1, 2 ]]); 42 | 43 | my $antidiag = Math::Matrix.new([[ 0, 0, 1 ], 44 | [ 0, 2, 0 ], 45 | [ 3, 0, 0 ],]); 46 | 47 | 48 | ok $zero.is-zero, "Is a zero matrix"; 49 | ok $z3.is-zero, "Another zero matrix"; 50 | nok $identity.is-zero, "Is not a zero matrix"; 51 | nok $ut.is-zero, "An upper triangular matrix is not zero"; 52 | 53 | ok $identity.is-identity, "Is an identity matrix"; 54 | nok $diagonal.is-identity, "diagonal is not an identity matrix"; 55 | nok $frobenius.is-identity, "a frobenius matrix is not an identity matrix"; 56 | nok $almostidentity.is-identity, "none square is not an identity matrix"; 57 | 58 | ok $matrixa.is-square, "Is a square matrix"; 59 | nok $matrixd.is-square, "Is not a square matrix"; 60 | 61 | ok $ut.is-triangular, "An upper triangular matrix is triangular"; 62 | ok $lt.is-triangular, "An lower triangular matrix is triangular"; 63 | nok $ut.is-triangular(:strict), "An upper triangular matrix is not strictly triangular"; 64 | nok $lt.is-triangular(:strict), "An lower triangular matrix is not strictly triangular"; 65 | ok $ut.is-triangular(:!strict), "An upper triangular matrix is not strictly triangular"; 66 | ok $lt.is-triangular(:!strict), "An lower triangular matrix is not strictly triangular"; 67 | ok $sut.is-triangular(:strict), "An strictly upper triangular matrix is strictly triangular"; 68 | ok $slt.is-triangular(:strict), "An strictly lower triangular matrix is strictly triangular"; 69 | nok $sut.is-triangular(:unit), "An strictly upper triangular matrix is not unit triangular"; 70 | nok $slt.is-triangular(:unit), "An strictly lower triangular matrix is not unit triangular"; 71 | ok $uut.is-triangular(:unit), "An unit upper triangular matrix is unit triangular"; 72 | ok $lut.is-triangular(:unit), "An unit lower triangular matrix is unit triangular"; 73 | nok $uut.is-triangular(:strict), "An unit upper triangular matrix is not a strict triangular"; 74 | nok $lut.is-triangular(:strict), "An unit lower triangular matrix is not a strict triangular"; 75 | nok $symmetric.is-triangular, "full ranked matrix is not triangular"; 76 | nok $symmetric.is-triangular(:strict),"full ranked matrix is not strictly triangular"; 77 | nok $ut.is-triangular(:unit,:strict), "no matrix can be unit and strict"; 78 | 79 | ok $ut.is-triangular(:upper), "Is an upper triangular matrix"; 80 | ok $ut.is-triangular(:upper,:!lower), "Is an upper triangular matrix is upper and not lower"; 81 | ok $ut.is-triangular(:!strict,:upper),"Is an upper triangular, none strict matrix"; 82 | nok $ut.is-triangular(:strict,:upper),"Upper triangular matrix is not strict"; 83 | ok $sut.is-triangular(:strict,:upper),"Is strictly upper triangular matrix"; 84 | ok $diagonal.is-triangular(:upper), "Diagonal are upper triangular"; 85 | ok $diagonal.is-triangular(:upper,:lower),"Diagonal matrix is an upper and lower triangular"; 86 | nok $matrixa.is-triangular(:upper), "Is not an upper triangular matrix"; 87 | nok $lt.is-triangular(:upper), "lower triangular is no upper triangular matrix"; 88 | 89 | ok $lt.is-triangular(:lower), "Is an lower triangular matrix"; 90 | ok $lt.is-triangular(:!strict,:lower),"Is an lower triangular, none strict matrix"; 91 | nok $lt.is-triangular(:strict,:lower),"Lower triangugal matrix is not strict"; 92 | ok $slt.is-triangular(:strict,:lower),"Is a strictly lower triangular matrix"; 93 | ok $diagonal.is-triangular(:lower), "Diagonal are lower triangular"; 94 | nok $matrixc.is-triangular(:lower), "Is not an lower diagonal matrix"; 95 | nok $ut.is-triangular(:lower), "upper triangular is no lower triangular matrix"; 96 | 97 | ok $frobenius.is-triangular(:atomic), "detect an atomic triangular matrix"; 98 | ok $identity.is-triangular(:atomic), "identity is a frobenius matrix"; 99 | nok $zero.is-triangular(:atomic), "zero is not a frobenius matrix"; 100 | nok $symmetric.is-triangular(:atomic), "a fully ranked matrix is not a frobenius matrix"; 101 | 102 | nok $almostidentity.is-diagonal, "none square is not an diagonal matrix"; 103 | ok $identity.is-diagonal, "Is an diagonal matrix"; 104 | ok $diagonal.is-diagonal, "Diagonal is an diagonal matrix"; 105 | ok $z3.is-diagonal, "square zero matrix is diagonal"; 106 | nok $zero.is-diagonal, "none square zero matrix is not diagonal"; 107 | nok $lt.is-diagonal, "Lower triangular matrix is no an diagonal matrix"; 108 | nok $ut.is-diagonal, "Upper triangular matrix is no an diagonal matrix"; 109 | 110 | ok $antidiag.is-anti-diagonal(), 'detected anti diagonal matrix'; 111 | nok $diagonal.is-anti-diagonal(), 'diagonal is not an anti diagonal matrix'; 112 | 113 | ok $tridiag.is-tridiagonal(), "detect tridiagonal matrix"; 114 | ok $diagonal.is-tridiagonal(), "diagonal is also tridiagonal matrix"; 115 | nok $lt.is-tridiagonal, "Lower triangular matrix is no an tridiagonal matrix"; 116 | nok $ut.is-tridiagonal, "Upper triangular matrix is no an tridiagonal matrix"; 117 | 118 | 119 | my $cat = Math::Matrix.new([[0,1],[1,0]]); 120 | ok $zero.is-diagonal-constant, "zero matrix is diagonal constant"; 121 | ok $identity.is-diagonal-constant, "identity matrix is diagonal constant"; 122 | nok $diagonal.is-diagonal-constant, "diagonal matrix is not diagonal constant"; 123 | nok $symmetric.is-diagonal-constant,"symmetric matrix is not diagonal constant"; 124 | ok $cat.is-catalecticant, "matrix is catalecticant"; 125 | nok $diagonal.is-catalecticant, "diagonal matrix is not catalecticant"; 126 | nok $zero.is-catalecticant, "only square matrices can be catalecticant"; 127 | 128 | 129 | nok $matrixa.is-diagonally-dominant, 'not diagonally dominant matrix'; 130 | ok $matrixc.is-diagonally-dominant, 'its diagonally dominant when all values are same'; 131 | nok $matrixc.is-diagonally-dominant(:strict), 'not strictly diagonally dominant when all values are same'; 132 | ok $identity.is-diagonally-dominant(:!strict), 'identity is always diagonally dominant'; 133 | ok $identity.is-diagonally-dominant(:strict), 'I is always strictly diagonally dominant'; 134 | ok $diagonal.is-diagonally-dominant(:strict, :along),'a diagonal matrix is col wise strictly diagonally dominant'; 135 | ok $diagonal.is-diagonally-dominant(:strict, :along), 'a diagonal matrix is row wise strictly diagonally dominant'; 136 | ok $diagonal.is-diagonally-dominant(:strict, :along), 'a diagonal matrix is always strictly diagonally dominant'; 137 | nok $lt.is-diagonally-dominant(:!strict, :along), 'this lower triangular matrix is not diagonally rowwise dominant'; 138 | nok $ut.is-diagonally-dominant(:!strict, :along), 'this upper triangular matrix is not diagonally rowwise dominant'; 139 | 140 | ok $diagonal.is-symmetric, "Is a symmetric matrix"; 141 | ok $symmetric.is-symmetric,"Is a symmetric matrix"; 142 | nok $matrixa.is-symmetric, "Is not a symmetric matrix"; 143 | 144 | ok Math::Matrix.new-zero(3).is-antisymmetric, "Zero matrix is antisymmetric"; 145 | ok Math::Matrix.new([[0,1],[-1,0]]).is-antisymmetric, "Special matrix is antisymmetric"; 146 | nok $symmetric.is-antisymmetric, "Symmetric is not antisymmetric matrix"; 147 | nok $matrixa.is-antisymmetric, "Default 1 to 4 matrix is not antisymmetric"; 148 | 149 | ok $diagonal.is-self-adjoint, "diagonal matrix is also hermetian"; 150 | ok $matrixh.is-self-adjoint, "this special matrix is hermetian"; 151 | nok $ut.is-self-adjoint, "a triangular matrix can not be hermetian"; 152 | 153 | ok $identity.is-unitary, "Identity matrix is unitary"; 154 | ok $matrixu.is-unitary, "special matrix is unitary"; 155 | 156 | ok $identity.is-orthogonal, "Is a orthogonal matrix"; 157 | nok $matrixa.is-orthogonal, "Is not a orthogonal matrix"; 158 | 159 | ok $identity.is-invertible, "Identity matrix is invertible"; 160 | ok $diagonal.is-invertible, "Diagonal matrix is invertible"; 161 | ok $diagonal.is-invertible, "A full ranked square matrix is invertible"; 162 | nok $zero.is-invertible, "Zero matrix is not invertible"; 163 | nok $matrixd.is-invertible, "Matrix with defect Is not invertible"; 164 | 165 | ok $identity.is-positive-definite, "Identity matrix is positive definite."; 166 | ok $identity.is-positive-semidefinite, "Identity matrix is positive semidefinite."; 167 | ok Math::Matrix.new([[2,-1,0],[-1,2,-1],[0,-1,2]]).is-positive-definite, "Special matrix is positive definite."; 168 | nok $zero.is-positive-definite, "zero matrix is not positive definite."; 169 | -------------------------------------------------------------------------------- /t/031-property-num.t: -------------------------------------------------------------------------------- 1 | use lib "lib"; 2 | use Test; 3 | use Math::Matrix; 4 | plan 9; 5 | 6 | subtest { 7 | plan 4; 8 | my $zero = Math::Matrix.new-zero(3,3); 9 | my $matrixa = Math::Matrix.new([[1,2],[3,4]]); 10 | my $matrixb = Math::Matrix.new([[1,2],[3,4],[5,6]]); 11 | 12 | ok $zero.size eqv (3,3), "Right size"; 13 | ok $matrixa.size eqv (2,2), "Right size too"; 14 | nok $matrixa.size eqv (5,5), "Wrong size"; 15 | ok $matrixb.size eqv (3,2), "Non square matrix, right size"; 16 | }, "Size"; 17 | 18 | subtest { 19 | plan 3; 20 | my $zero = Math::Matrix.new-zero(3,4); 21 | my $identity = Math::Matrix.new-identity(3); 22 | my $matrix = Math::Matrix.new([[1,2,3],[2,4,6],[3,6,9]]); 23 | 24 | ok $zero.density == 0 ,"Zero matrix has density of 0"; 25 | ok $identity.density == 1/3 ,"Identity matrix has density of 1/size"; 26 | ok $matrix.density == 1 ,"full matrix has density of 1"; 27 | }, "Density"; 28 | 29 | subtest { 30 | plan 15; 31 | my $zero = Math::Matrix.new-zero(3,4); 32 | my $identity = Math::Matrix.new-identity(3); 33 | my $matrix = Math::Matrix.new([[1,2,3],[2,4,6],[3,6,9]]); 34 | my $matrixb = Math::Matrix.new([[1,0],[3,4],[5,6]]); 35 | my $matrixc = Math::Matrix.new([[1,2,3],[0,4,5]]); 36 | 37 | ok $zero.upper-bandwith == 0 ,"Zero matrix has upper bandwith of 0"; 38 | ok $zero.lower-bandwith == 0 ,"Zero matrix has lower bandwith of 0"; 39 | ok $zero.bandwith == 0 ,"Zero matrix has bandwith of 0"; 40 | ok $identity.upper-bandwith == 0,"Identity matrix has upper bandwith of 0"; 41 | ok $identity.lower-bandwith == 0,"Identity matrix has lower bandwith of 0"; 42 | ok $identity.bandwith == 0 ,"Identity matrix has bandwith of 0"; 43 | ok $matrix.upper-bandwith == 2, "full matrix has upper bandwith of 2"; 44 | ok $matrix.lower-bandwith == 2, "full matrix has lower bandwith of 2"; 45 | ok $matrix.bandwith == 2 ,"full matrix has bandwith of 2"; 46 | ok $matrixb.upper-bandwith == 0, "custom matrix has upper bandwith of 0"; 47 | ok $matrixb.lower-bandwith == 2, "custom matrix has lower bandwith of 2"; 48 | ok $matrixb.bandwith == 2 , "custom matrix has bandwith of 2"; 49 | ok $matrixc.upper-bandwith == 2, "custom mirror matrix has upper bandwith of 2"; 50 | ok $matrixc.lower-bandwith == 0, "custom mirror matrix has lower bandwith of 0"; 51 | ok $matrixc.bandwith == 2 , "custom mirror matrix has bandwith of 2"; 52 | }, "Bandwith"; 53 | 54 | 55 | subtest { 56 | plan 2; 57 | my $matrix = Math::Matrix.new([[1,2,5,4],[1,2,3,2],[9,8,4,1],[1,3,4,6]]); 58 | ok $matrix.trace() == 13 , "Trace of a Matrix"; 59 | my $matrix2 = Math::Matrix.new([[1,2,5,4],[1,2,3,2],[9,8,4,1]]); 60 | dies-ok { $matrix2.trace() } , "Non square matrix, no trace"; 61 | }, "Trace"; 62 | 63 | subtest { 64 | plan 7; 65 | my $zero = Math::Matrix.new-zero(3,3); 66 | my $identity = Math::Matrix.new-identity(3); 67 | my $diagonal = Math::Matrix.new-diagonal([1,2,3]); 68 | my $matrix = Math::Matrix.new([[1,2,5,4],[1,2,3,2],[9,8,4,1],[1,3,4,6]]); 69 | my $matrix2 = Math::Matrix.new([[1,2,5,4],[1,2,3,2],[9,8,4,1]]); 70 | 71 | dies-ok { $matrix2.determinant() } , "Non square matrix, has no determinant"; 72 | ok $zero.determinant() == 0 , "Determinant of zero matrix is 0"; 73 | ok $identity.determinant() == 1 , "Determinant of identity matrix is 1"; 74 | ok $diagonal.determinant() == 6 , "det of diagonal matrix is product of diagonal elements"; 75 | ok $matrix.determinant() == -72 , "Determinant of a Matrix"; 76 | 77 | my $a = Math::Matrix.new([[7, 3, 7, 1, 1, 4], [9, 7, 6, 1, 9, 1], [9, 6, 2, 5, 5, 6], [6, 0, 3, 5, 1, 3], [0, 5, 0, 0, 5, 7], [4, 2, 7, 6, 1, 9]]); 78 | ok $a.det == -33618, "6x6 matrix determinant is correct (use Decomposition behind the scene)"; 79 | 80 | ok( ( | $matrix | == -72), 'unicode determinant operator'); 81 | }, "Determinant"; 82 | 83 | 84 | subtest { 85 | plan 4; 86 | my $zero = Math::Matrix.new-zero(3,4); 87 | my $identity = Math::Matrix.new-identity(3); 88 | my $diagonal = Math::Matrix.new-diagonal([1,2,3]); 89 | my $matrix = Math::Matrix.new([[1,2,3],[2,4,6],[3,6,9]]); 90 | 91 | ok $zero.rank == 0 ,"Rank of Zero Matrix"; 92 | ok $identity.rank == 3 ,"Identity has full rank"; 93 | ok $diagonal.rank == 3 ,"Diagonal has full rank"; 94 | ok $matrix.rank == 1 ,"Custom Matrinx with larger nullity has lesser rank"; 95 | }, "Rank"; 96 | 97 | subtest { 98 | plan 4; 99 | my $zero = Math::Matrix.new-zero(3,4); 100 | my $identity = Math::Matrix.new-identity(3); 101 | my $diagonal = Math::Matrix.new-diagonal([1,2,3]); 102 | my $matrix = Math::Matrix.new([[1,2,3],[2,4,6],[3,6,9]]); 103 | 104 | ok $zero.nullity == 3 ,"Zero matrix has full nullity"; 105 | ok $identity.nullity == 0 ,"Identity has no nullity"; 106 | ok $diagonal.nullity == 0 ,"Diagonal has no nullity"; 107 | ok $matrix.nullity == 2 ,"Custom matrix with larger nullity has lesser rank"; 108 | }, "Nullity"; 109 | 110 | subtest { 111 | plan 29; 112 | my $zero = Math::Matrix.new-zero(3,4); 113 | my $identity = Math::Matrix.new-identity(3); 114 | my $diagonal = Math::Matrix.new-diagonal([1,2,3]); 115 | my $matrix = Math::Matrix.new([[1,2,3],[2,4,6],[3,6,9]]); 116 | my $m1 = Math::Matrix.new([[1]]); 117 | my $m2 = Math::Matrix.new([[1,2],[3,4]]); 118 | 119 | dies-ok { $zero.norm(0) } ,"there is no 0 norm"; 120 | dies-ok { $zero.norm(1,0) } ,"there is no n,0 norm"; 121 | dies-ok { $zero.norm(0.1) } ,"p accepts only whole numbers"; 122 | dies-ok { $zero.norm(1,0.1) } ,"q accepts only whole numbers"; 123 | ok $zero.norm == 0 ,"Zero matrix is 0 in any norm"; 124 | ok $identity.norm == sqrt(3) ,"Identity matrix norm equals rank"; 125 | ok $diagonal.norm == sqrt(14) ,"Norm of diagonal matrix is equal trace in euclid space"; 126 | ok $diagonal.norm(:p<2>) == sqrt(14), "2,1 Norm with one default value"; 127 | ok $diagonal.norm(:p<2>,:q<1>) == 6,"2,1 Norm with no default value"; 128 | ok $zero.norm(:p<1>,:q<1>) == 0 ,"Zero matrix is 0 in any norm"; 129 | ok $matrix.norm(:p<1>,:q<1>)== 36 ,"1,1 norm is just sum of elements"; 130 | ok $zero.norm(:p<2>,:q<2>) == 0 ,"Zero matrix is 0 in 2,2 norm too"; 131 | ok $diagonal.norm(:p<2>,:q<2>) == sqrt(14),"Frobenius norm"; 132 | 133 | ok $zero.norm('max') == 0 ,"max norm of zero == 0"; 134 | ok $matrix.norm('max') == 9 ,"max norm"; 135 | ok ($matrix *3).norm('max')== 9*3 ,"max norm is homogenic"; 136 | ok $zero.norm('row-sum') == 0 ,"row sum norm of zero == 0"; 137 | ok $matrix.norm('row-sum') == 18 ,"row sum norm"; 138 | ok ($matrix *3).norm('row-sum') == 54,"row sum norm is homogenic"; 139 | ok $zero.norm('column-sum') == 0 ,"column sum norm of zero == 0"; 140 | ok $matrix.norm('column-sum') == 18,"column sum norm"; 141 | ok ($matrix *3).norm('column-sum') == 54,"column sum norm is homogenic"; 142 | 143 | ok ($diagonal dot $matrix).norm <= $diagonal.norm * $matrix.norm, "Cauchy-Schwarz inequality for L2 norm"; 144 | ok ($diagonal dot $matrix).norm(:p<2>,:q<3>) <= $diagonal.norm(:p<2>,:q<3>) * $matrix.norm(:p<2>,:q<3>), "Cauchy-Schwarz inequality for 2,3 norm"; 145 | ok ($diagonal dot $matrix).norm('max') <= $diagonal.norm('max') * $matrix.norm('max'), "Cauchy-Schwarz inequality for maximum norm"; 146 | ok ($diagonal dot $matrix).norm('row-sum') <= $diagonal.norm('row-sum') * $matrix.norm('row-sum'), "Cauchy-Schwarz inequality for rowsum norm"; 147 | ok ($diagonal dot $matrix).norm('column-sum') <= $diagonal.norm('column-sum') * $matrix.norm('column-sum'), "Cauchy-Schwarz inequality for columnsum norm"; 148 | 149 | ok ‖ $m1 ‖ == 1, 'norm op on simplest matrix'; 150 | ok ‖ $m2 ‖ == 5.477225575051661, 'norm op on default matrix'; 151 | 152 | 153 | }, "Norm"; 154 | 155 | 156 | subtest { 157 | my $matrixa = Math::Matrix.new([[1,2],[3,4]]); 158 | my $matrixm = Math::Matrix.new([[Bool,2.3],[3-i, 4.1.FatRat]]); 159 | my $matrixr = Math::Matrix.new([[4e-3,2.3],[12, 4.1.FatRat]]); 160 | ok $matrixa.narrowest-element-type ~~ Int, "got narrowest type of default example correct"; 161 | ok $matrixa.widest-element-type ~~ Int, "got widest type of default example correct"; 162 | ok $matrixm.narrowest-element-type ~~ Bool, "got narrowest type of mixed matrix correct"; 163 | ok $matrixm.widest-element-type ~~ Complex, "got widest type of mixed matrix correct"; 164 | ok $matrixr.narrowest-element-type ~~ Int, "got narrowest type of mostly rational matrix correct"; 165 | ok $matrixr.widest-element-type ~~ FatRat, "got widest type of mostly rational typed matrix correct"; 166 | }, 'Element Type'; 167 | -------------------------------------------------------------------------------- /t/040-derived.t: -------------------------------------------------------------------------------- 1 | use lib "lib"; 2 | use Test; 3 | use Math::Matrix; 4 | plan 5; 5 | 6 | subtest { 7 | plan 3; 8 | my $matrix = Math::Matrix.new([[1,2],[3,4]]); 9 | ok $matrix.negated() ~~ Math::Matrix.new([[ -1 , -2 ],[ -3 , -4 ]]), "Negative of a matrix"; 10 | ok $matrix.negated().negated() ~~ $matrix, "Double negative does nothing"; 11 | ok - $matrix ~~ Math::Matrix.new([[ -1 , -2 ],[ -3 , -4 ]]), "negate by op"; 12 | }, "Negation"; 13 | 14 | subtest { 15 | plan 3; 16 | my $matrix = Math::Matrix.new([[1,2],[3,4]]); 17 | my $cmatrix = Math::Matrix.new([[1+i,2],[3-2i,4]]); 18 | my $ccmatrix = Math::Matrix.new([[1-i,2],[3+2i,4]]); 19 | ok $matrix.conjugated() ~~ $matrix, "conjugation on Int matrix is identity"; 20 | ok $cmatrix.conj().conj() ~~ $cmatrix, "Double conjugation is identity"; 21 | ok $cmatrix.conjugated() ~~ $ccmatrix, "does conjugation right"; 22 | }, "Conjugation"; 23 | 24 | subtest { 25 | plan 2; 26 | my $matrix = Math::Matrix.new([[1,2],[3,4]]); 27 | my $expected = Math::Matrix.new([[1,3],[2,4]]); 28 | 29 | ok $matrix.T ~~ $expected, "Transposition result correct"; 30 | ok $matrix.T.T ~~ $matrix, "Double tranposition does nothing"; 31 | }, "Tranposition"; 32 | 33 | subtest { 34 | plan 8; 35 | my $identity = Math::Matrix.new-identity(3); 36 | my $diagonal = Math::Matrix.new-diagonal([1,2,3]); 37 | my $matrixa = Math::Matrix.new([[1,2,3],[2,4,6],[3,6,9]]); 38 | my $matrixb = Math::Matrix.new([[1,2],[3,4]]); 39 | my $expectb = Math::Matrix.new([[-2, 1],[1.5, -0.5]]); 40 | my $matrixc = Math::Matrix.new([[1,1,0],[0,1,1],[1,0,1]]); 41 | my $expectc = Math::Matrix.new([[0.5,-.5,0.5],[0.5,.5,-0.5],[-0.5,0.5,0.5]]); 42 | my $expectd = Math::Matrix.new([[1,0,0],[0,0.5,0],[0,0,1/3]]); 43 | 44 | dies-ok {Math::Matrix.zero(3,4).inverted}, "only square matrices can be inverted"; 45 | dies-ok {$matrixa.inverted}, "only none singular matrices can be inverted"; 46 | ok $matrixb.inverted ~~ $expectb, "Inversion works correctly"; 47 | ok $matrixb.inverted.inverted ~~ $matrixb, "Double Inversion does nothing"; 48 | ok $identity.inverted ~~ $identity,"Inverted identity is identity"; 49 | ok $matrixc.inverted ~~ $expectc, "Inversion works correctly"; 50 | ok $diagonal.inverted ~~ $expectd, "Inversion works correctly for diagonal"; 51 | ok $matrixb ** -1 ~~ $expectb, "inverting by operator works too"; 52 | }, "Inversion"; 53 | 54 | subtest { 55 | plan 2; 56 | my $matrix = Math::Matrix.new( 57 | [[1, 2, -1, -4], 58 | [2, 3, -1, -11], 59 | [-2, 0, -3, 22]] 60 | ); 61 | my $expected = Math::Matrix.new([ 62 | [1, 0, 0, -8], 63 | [0, 1, 0, 1], 64 | [0, 0, 1, -2] 65 | ]); 66 | ok $matrix.reduced-row-echelon-form() ~~ $expected, "Rref is correct"; 67 | ok $matrix.rref() ~~ $expected, "Rref is correct, using shortcut"; 68 | }, "row echelon"; 69 | -------------------------------------------------------------------------------- /t/041-decomposition.t: -------------------------------------------------------------------------------- 1 | use lib "lib"; 2 | use Test; 3 | use Math::Matrix; 4 | plan 2; 5 | 6 | subtest { 7 | plan 9; 8 | my $mat = Math::Matrix.new([[7, 3, 7, 1, 1, 4], 9 | [9, 7, 6, 1, 9, 1], 10 | [9, 6, 2, 5, 5, 6], 11 | [6, 0, 3, 5, 1, 3], 12 | [0, 5, 0, 0, 5, 7], 13 | [4, 2, 7, 6, 1, 9]]); 14 | my $matrix = Math::Matrix.new([[4,0,1],[2,1,0],[2,2,3]]); 15 | my $expectedL = Math::Matrix.new([[4,0,0],[2,1,0],[2,2,7/2]]); 16 | my $expectedU = Math::Matrix.new([[1,0,1/4],[0,1,-1/2],[0,0,1]]); 17 | my ($L, $D, $U, $P); 18 | 19 | my ($L1, $U1) = $mat.LU-decomposition(); 20 | ok $L1 dot $U1 ~~ $mat, "LU = A"; 21 | # say $U1 dot $L1; 22 | ok $L1.is-triangular(:lower, :unit), "L is lower unit triangular"; 23 | ok $U1.is-triangular(:upper), "U is upper triangular"; 24 | nok $U1.is-triangular(:unit), "U is not unit"; 25 | 26 | my ($L2, $U2) = $mat.LU-decomposition( :Crout ); 27 | ok $L2 dot $U2 ~~ $mat, "LU = A Crout"; 28 | ok $L2.is-triangular(:lower ), "L is lower triangular"; 29 | ok $U2.is-triangular(:upper, :unit), "U is upper unit triangular"; 30 | nok $L2.is-triangular(:unit), "L is not unit"; 31 | dies-ok {$mat.LU-decomposition( :Crout, :diagonal )}, "attributes :Crout and :diagonal are mutually exclusive"; 32 | 33 | # die "good"; 34 | # NYI 35 | # ( $L, $U, $P ) = $matrix.LU-decomposition(); 36 | # ok $L dot $U ~~ $P dot $matrix, "LU = PA"; 37 | 38 | # ($L, $U) = $matrix.decompositionLUCrout(); 39 | # ok ( $L ~~ $expectedL and $U ~~ $expectedU ) , "L and U are correct"; 40 | # ok ($L dot $U) ~~ $matrix, "LU is equal to original matrix"; 41 | 42 | }, "LU"; 43 | 44 | 45 | subtest { 46 | plan 8; 47 | 48 | my $zero = Math::Matrix.new-zero(3,4); 49 | my $identity = Math::Matrix.new-identity(3); 50 | my $diagonal = Math::Matrix.new-diagonal([1,4,9]); 51 | my $diagonalD = Math::Matrix.new-diagonal([1,2,3]); 52 | my $simple = Math::Matrix.new([[1,3],[3,25]]); 53 | my $simpleD = Math::Matrix.new([[1,0],[3,4]]); 54 | 55 | dies-ok {$zero.Cholesky-decomposition}, "no decomposition of non-square matrices"; 56 | dies-ok {Math::Matrix.new([[1,1,3],[5,2,1],[5,3,4]]).Cholesky-decomposition}, 57 | 'no decomposition of non-diagonal dominant matrices'; 58 | ok $identity.Cholesky-decomposition ~~ $identity, "decomposed identity is identity"; 59 | ok $diagonal.Cholesky-decomposition ~~ $diagonalD, "in decomposed diagonal matrix cell values get squared"; 60 | ok $simple.Cholesky-decomposition ~~ $simpleD, "simple custom Cholesky decomposition"; 61 | ok $simple.Cholesky-decomposition(:!diagonal) ~~ $simpleD,"format without diagonal is default"; 62 | my ($G) = $simple.Cholesky-decomposition(); 63 | ok $G dot $G.T ~~ $simple, "Cholesky without diagonal matrix is a working decomposition"; 64 | my ($L, $D) = $simple.Cholesky-decomposition(:diagonal); 65 | ok $L dot $D dot $L.T ~~ $simple, "Cholesky with a diagonal matrix is a working decomposition"; 66 | }, "Choleski"; 67 | -------------------------------------------------------------------------------- /t/050-math-operation.t: -------------------------------------------------------------------------------- 1 | use lib "lib"; 2 | use Test; 3 | use Math::Matrix; 4 | plan 8; 5 | 6 | my $matrix = Math::Matrix.new([[1,2],[3,4]]); 7 | my $m1 = Math::Matrix.new([[1]]); 8 | 9 | subtest { 10 | plan 1; 11 | my $expected1 = Math::Matrix.new([[2,3],[4,5]]); 12 | ok $matrix.add(1) ~~ $expected1, "add a scalar to all cells"; 13 | }, "Scalar Addition"; 14 | 15 | 16 | subtest { 17 | plan 11; 18 | my $expected1 = Math::Matrix.new([[3,5],[3,4]]); 19 | my $expected2 = Math::Matrix.new([[1,2],[5,7]]); 20 | my $expected3 = Math::Matrix.new([[3,2],[6,4]]); 21 | my $expected4 = Math::Matrix.new([[1,4],[3,7]]); 22 | 23 | ok $matrix.add(row => 0, [2,3]) ~~ $expected1, "add a row"; 24 | ok $matrix.add(row => 1, [2,3]) ~~ $expected2, "add another row"; 25 | ok $matrix.add(row => 1, (2,3)) ~~ $expected2, "list syntax for adding row"; 26 | ok $matrix.add([2,3], row => 1) ~~ $expected2, "column number can be second argument"; 27 | dies-ok { $matrix.add(row => 3, [1,2]) }, "row index out of bound"; 28 | dies-ok { $matrix.add(row => 1, [1]) }, "vector size out of bound"; 29 | 30 | ok $matrix.add( column => 0, [2,3])~~ $expected3,"add a column"; 31 | ok $matrix.add( column => 1, [2,3])~~ $expected4,"add another column"; 32 | ok $matrix.add( column => 1, (2,3))~~ $expected4,"list syntax is also good for adding column"; 33 | dies-ok { $matrix.add(column => 3, [1,2]) }, "column index out of bound"; 34 | dies-ok { $matrix.add(column => 1, [1,]) }, "vector size out of bound"; 35 | }, "Vector Addition"; 36 | 37 | 38 | subtest { 39 | plan 5; 40 | my $matrix2 = Math::Matrix.new([[4,3],[2,1]]); 41 | my $expected = Math::Matrix.new([[5,5],[5,5]]); 42 | 43 | ok $matrix.add( $matrix2 ) ~~ $expected, "Sum of matrices"; 44 | ok $matrix2.add( $matrix ) ~~ $expected, "Sum of matrices reversed"; 45 | ok $matrix + $matrix2 ~~ $expected, "Sum of matrices using + operator"; 46 | ok $matrix2 + $matrix ~~ $expected, "Sum of matrices using + operator reversed"; 47 | 48 | dies-ok { $matrix.add(Math::Matrix.new([[1]]))}, "matrix size out of bound"; 49 | }, "Matrix Addition"; 50 | 51 | 52 | subtest { 53 | plan 6; 54 | my $expected1 = Math::Matrix.new([[3,6],[3,4]]); 55 | my $expected2 = Math::Matrix.new([[1,2],[9,12]]); 56 | my $expected3 = Math::Matrix.new([[3,2],[9,4]]); 57 | my $expected4 = Math::Matrix.new([[1,6],[3,12]]); 58 | 59 | ok $matrix.multiply(row => 0, 3) ~~ $expected1, "multiply a row"; 60 | ok $matrix.multiply(row => 1, 3) ~~ $expected2, "multiply another row"; 61 | dies-ok { $matrix.multiply(row => 3, 2) }, "row index out of bound"; 62 | 63 | ok $matrix.multiply( column => 0, 3) ~~ $expected3,"multiply a column"; 64 | ok $matrix.multiply( column => 1, 3) ~~ $expected4,"multiply another column"; 65 | dies-ok { $matrix.multiply( column => 3, 2) }, "row index out of bound"; 66 | }, "Partial Scalar Multiplication"; 67 | 68 | subtest { 69 | plan 4; 70 | my $expected = Math::Matrix.new([[2.2, 4.4],[6.6, 8.8]]); 71 | my $expected2 = Math::Matrix.new([[1, 2],[3, 8.8]]); 72 | ok $matrix.multiply( 2.2 ) ~~ $expected, "multiplication with real working"; 73 | ok $matrix * 2.2 ~~ $expected, "scalar multiplication with operator *"; 74 | ok 2.2 * $matrix ~~ $expected, "operator *, reverse args"; 75 | ok $matrix.multiply(row => 1, column => 1, 2.2 ) ~~ $expected2, "multiply only one cell"; 76 | }, "Scalar Multiplication"; 77 | 78 | subtest { 79 | plan 3; 80 | my $matrix = Math::Matrix.new([[1,2],[3,4]]); 81 | my $matrix2 = Math::Matrix.new([[4,3],[2,1]]); 82 | my $expected = Math::Matrix.new([[ 4 , 6 ],[ 6 , 4 ]]); 83 | 84 | ok $matrix.multiply( $matrix2 ) ~~ $expected, "Multiplication of matrices (element by element)"; 85 | ok $matrix * $matrix2 ~~ $expected, "Multiplication of matrices using * operator"; 86 | dies-ok {$matrix.multiply(Math::Matrix.new([[1]]))}, "matrix size out of bound"; 87 | }, "Cellwise Multiplication"; 88 | 89 | 90 | subtest { 91 | plan 8; 92 | my $a = Math::Matrix.new( [[1,2,3],[4,5,6]] ); 93 | my $b = Math::Matrix.new( [[7,8],[9,10],[11,12]] ); 94 | my $p = Math::Matrix.new([[58,64],[139,154]]); 95 | my $matrix = Math::Matrix.new([[1,2],[3,4]]); 96 | my $identity = Math::Matrix.new-identity(2); 97 | 98 | ok $a.dot-product( $b ) ~~ $p, "Simple multiplication check"; 99 | ok ($a ⋅ $b) ~~ $p, "Simple multiplication check with ⋅ operator"; 100 | ok ($a dot $b) ~~ $p, "Simple multiplication check with ⋅ operator, texas form"; 101 | ok $matrix ** 0 ~~ $identity, "times one means no multiplication"; 102 | ok $matrix ** 1 ~~ $matrix, "times one means no multiplication"; 103 | ok $matrix ** 2 ~~ $matrix dot $matrix, "power operator works too"; 104 | 105 | my $c = Math::Matrix.new( [[7,8],[9,10],[11,12],[13,14]] ); 106 | dies-ok { $a ⋅ $c } , "Matrices can't be multiplied, first matrix column count should be equal to second matrix row count"; 107 | dies-ok { $a.dot-product( $c ) } , "Matrices can't be multiplied, first matrix column count should be equal to second matrix row count"; 108 | }, "Dot Product"; 109 | 110 | 111 | subtest { 112 | plan 4; 113 | my $a = Math::Matrix.new( [[1,2],[3,4]] ); 114 | my $b = Math::Matrix.new( [[5,6],[7,8]] ); 115 | my $p = Math::Matrix.new( [[5,6,10,12],[7,8,14,16],[15,18,20,24],[21,24,28,32]] ); 116 | my $i = Math::Matrix.new( [[1]] ); 117 | my $z3 = Math::Matrix.new-zero(3); 118 | my $z12 = Math::Matrix.new-zero(12); 119 | 120 | ok $i.tensor-product( $i ) ~~ $i, "Trivial multiplication check"; 121 | ok $a.tensor-product( $b ) ~~ $p, "Simple multiplication check"; 122 | ok $z3.tensor-product($p ) ~~ $z12, "check for richt dimension expansion on larger matrix"; 123 | ok ($a X* $b) ~~ $p, "Simple multiplication check with x operator"; 124 | }, "Tensor Product"; 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /t/051-list-like-operation.t: -------------------------------------------------------------------------------- 1 | use lib "lib"; 2 | use Test; 3 | use Math::Matrix :ALL; 4 | plan 5; 5 | 6 | my $morecols = Math::Matrix.new( [[1,2,3],[4,5,6]] ); 7 | my $morerows = Math::Matrix.new( [[7,8],[9,10],[11,12]] ); 8 | my $id = Math::Matrix.new-identity( 3 ); 9 | 10 | subtest { 11 | plan 2; 12 | ok $morecols.elems == 6, "right number of elements"; 13 | ok $id.elems == 9, "right number of elements too"; 14 | }, "Elems"; 15 | 16 | 17 | subtest { 18 | plan 3; 19 | ok $morecols.elem(1..6), "All cell values are cells within asked range"; 20 | ok $morerows.elem(1..22), "All cell values are cells within way larger range"; 21 | nok $morecols.elem(7..12), "There are cells not within asked range"; 22 | }, "Elem"; 23 | 24 | subtest { 25 | plan 4; 26 | ok $morecols.cont( 3 ), "value 3 is in a matrix cell"; 27 | nok $morecols.cont( 7 ), "value 7 is in a matrix cell"; 28 | ok $morecols.cont(2..4), "There are cells within asked range"; 29 | nok $morecols.cont(7..12), "There are no cells within asked range"; 30 | }, "Cont"; 31 | 32 | 33 | subtest { 34 | plan 4; 35 | ok $morecols.map( * - 1 ) ~~ MM[[0,1,2],[3,4,5]], "simple mapping"; 36 | ok $morecols.map({$^v %% 2 ?? 1 !! 0}) ~~ MM[[0,1,0],[1,0,1]], "constructing binary map"; 37 | ok $morecols.map(rows=>1..1, {$_ + 1}) ~~ MM[[1,2,3],[5,6,7]], "mapping row"; 38 | ok $morecols.map(columns => 0..0, {0}) ~~ MM[[0,2,3],[0,5,6]], "mapping column"; 39 | }, "Map"; 40 | 41 | 42 | subtest { 43 | plan 4; 44 | ok $morecols.reduce( &[+] ) == (21), "simple cell sum"; 45 | ok $morecols.reduce-rows( &[+] ) == (6,15), "simple row sum"; 46 | ok $morecols.reduce-columns(&[*])== (4,10,18), "simple column product"; 47 | ok $id.reduce-rows( &[>] ) == (True, False, False), "question if row is sorted"; 48 | }, "Reduce"; 49 | -------------------------------------------------------------------------------- /t/052-structural-operation.t: -------------------------------------------------------------------------------- 1 | use lib "lib"; 2 | use Test; 3 | use Math::Matrix; 4 | plan 4; 5 | 6 | my $a = Math::Matrix.new( [[1,2,3],[2,3,4],[3,4,5]] ); 7 | my $b = Math::Matrix.new( [[7,8],[9,10]] ); 8 | my $i = Math::Matrix.new-identity( 3 ); 9 | 10 | subtest { 11 | plan 8; 12 | ok $a.move-row(0, 2) ~~ Math::Matrix.new([[2,3,4],[3,4,5],[1,2,3]]), "move a row"; 13 | ok $a.move-row(2=>1) ~~ Math::Matrix.new([[1,2,3],[3,4,5],[2,3,4]]), "move a another row, pair form"; 14 | dies-ok { ok $a.move-row(5, 1); }, "source row number out of bound"; 15 | dies-ok { ok $a.move-row(1, -3); }, "target row number out of bound"; 16 | 17 | ok $a.move-column(0, 2) ~~ Math::Matrix.new([[2,3,1],[3,4,2],[4,5,3]]), "move a column"; 18 | ok $a.move-column(2=>1) ~~ Math::Matrix.new([[1,3,2],[2,4,3],[3,5,4]]), "move a another column, pair form"; 19 | dies-ok { ok $a.move-column(3, 1); }, "source column number out of bound"; 20 | dies-ok { ok $a.move-column(1, -5); }, "target column number out of bound"; 21 | 22 | }, "Move"; 23 | 24 | 25 | subtest { 26 | plan 8; 27 | ok $a.swap-rows(0, 2) ~~ Math::Matrix.new([[3,4,5],[2,3,4],[1,2,3]]), "swap two rows"; 28 | ok $a.swap-rows(2, 1) ~~ Math::Matrix.new([[1,2,3],[3,4,5],[2,3,4]]), "swap another two rows"; 29 | dies-ok { ok $a.swap-rows(5, 1); }, "first row number out of bound"; 30 | dies-ok { ok $a.swap-rows(1, -3); }, "second row number out of bound"; 31 | 32 | ok $a.swap-columns(0, 2) ~~ Math::Matrix.new([[3,2,1],[4,3,2],[5,4,3]]),"swap two columns"; 33 | ok $a.swap-columns(2, 1) ~~ Math::Matrix.new([[1,3,2],[2,4,3],[3,5,4]]),"swap another two columns"; 34 | dies-ok { ok $a.swap-columns(3, 1); }, "first column number out of bound"; 35 | dies-ok { ok $a.swap-columns(1, -5); }, "second column number out of bound"; 36 | 37 | }, "Swap"; 38 | 39 | 40 | subtest { 41 | my $answer1 = Math::Matrix.new([[1,0,0],[0,1,0],[0,0,1],[1,2,3],[2,3,4],[3,4,5]]); 42 | my $answer2 = Math::Matrix.new([[1,2,3],[1,0,0],[0,1,0],[0,0,1],[2,3,4],[3,4,5]]); 43 | my $answer3 = Math::Matrix.new([[1,2,3],[1,0,0],[0,1,0],[0,0,1],[3,4,5]]); 44 | my $answer4 = Math::Matrix.new([[1,2,3],[2,3,4],[3,4,5],[1,0,0],[0,1,0],[0,0,1]]); 45 | 46 | plan 12; 47 | ok $a.splice-rows(0,0,$i) ~~ $answer1, "prepend rows of a matrix"; 48 | ok $a.splice-rows(0,0,[[1,0,0],[0,1,0],[0,0,1]]) ~~ $answer1, "prepend rows of data"; 49 | 50 | dies-ok { $a.splice-rows(10) }, "splicing from out of bound index"; 51 | dies-ok { $a.splice-rows(1,-20) }, "try to splice too little"; 52 | dies-ok { $a.splice-rows(0,0,$b) }, "can not splice rows with matrix with different size"; 53 | dies-ok { $a.splice-rows(0,0,[[1]])}, "can not splice rows with data matrix with different size"; 54 | 55 | ok $a.splice-rows(1,0,$i) ~~ $answer2, "insert rows of matrix"; 56 | ok $a.splice-rows(1,0,[[1,0,0],[0,1,0],[0,0,1]]) ~~ $answer2, "insert rows of data"; 57 | ok $a.splice-rows(1,1,$i) ~~ $answer3, "replace rows of matrix"; 58 | ok $a.splice-rows(1,1,[[1,0,0],[0,1,0],[0,0,1]]) ~~ $answer3, "replace rows of data"; 59 | ok $a.splice-rows(-1,0,$i) ~~ $answer4, "append rows of matrix"; 60 | ok $a.splice-rows(-1,0,[[1,0,0],[0,1,0],[0,0,1]]) ~~ $answer4, "append rows of data"; 61 | 62 | }, "Splice Rows"; 63 | 64 | 65 | subtest { 66 | my $answer1 = Math::Matrix.new([[1,0,0,1,2,3],[0,1,0,2,3,4],[0,0,1,3,4,5]]); 67 | my $answer2 = Math::Matrix.new([[1,1,0,0,2,3],[2,0,1,0,3,4],[3,0,0,1,4,5]]); 68 | my $answer3 = Math::Matrix.new([[1,1,0,0, 3],[2,0,1,0, 4],[3,0,0,1, 5]]); 69 | my $answer4 = Math::Matrix.new([[1,2,3,1,0,0],[2,3,4,0,1,0],[3,4,5,0,0,1]]); 70 | 71 | plan 12; 72 | ok $a.splice-columns(0,0,$i) ~~ $answer1, "prepend columns of a matrix"; 73 | ok $a.splice-columns(0,0,[[1,0,0],[0,1,0],[0,0,1]]) ~~ $answer1, "prepend columns of data"; 74 | 75 | dies-ok { $a.splice-columns(10) }, "splicing from out of bound index"; 76 | dies-ok { $a.splice-columns(1,-20) }, "try to splice too little"; 77 | dies-ok { $a.splice-columns(0,0,$b) }, "can not splice columns with matrix with different size"; 78 | dies-ok { $a.splice-columns(0,0,[[1]])}, "can not splice columns with data matrix of different size"; 79 | 80 | ok $a.splice-columns(1,0,$i) ~~ $answer2, "insert columns of matrix"; 81 | ok $a.splice-columns(1,0,[[1,0,0],[0,1,0],[0,0,1]]) ~~ $answer2, "insert columns of data"; 82 | ok $a.splice-columns(1,1,$i) ~~ $answer3, "replace columns of matrix"; 83 | ok $a.splice-columns(1,1,[[1,0,0],[0,1,0],[0,0,1]]) ~~ $answer3, "replace columns of data"; 84 | ok $a.splice-columns(-1,0,$i) ~~ $answer4, "append columns of matrix"; 85 | ok $a.splice-columns(-1,0,[[1,0,0],[0,1,0],[0,0,1]]) ~~ $answer4, "append columns of data"; 86 | 87 | }, "Splice Columns"; 88 | -------------------------------------------------------------------------------- /update_readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Generating README.md" 4 | 5 | echo "[![Build Status](https://travis-ci.org/pierre-vigier/Perl6-Math-Matrix.svg?branch=master)](https://travis-ci.org/pierre-vigier/Perl6-Math-Matrix)" >README.md 6 | 7 | # echo "[![Build status](https://ci.appveyor.com/api/projects/status/github/pierre-vigier/Perl6-Math-Matrix?svg=true)](https://ci.appveyor.com/project/pierre-vigier/Perl6-Math-Matrix/branch/master)\n" >>README.md 8 | 9 | perl6 --doc=Markdown lib/Math/Matrix.pod >>README.md 10 | 11 | --------------------------------------------------------------------------------