MatrixAxiomsTest.php 106 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694
  1. <?php
  2. namespace MathPHP\Tests\LinearAlgebra\Matrix\Numeric;
  3. use MathPHP\LinearAlgebra\MatrixFactory;
  4. use MathPHP\LinearAlgebra\NumericSquareMatrix;
  5. use MathPHP\LinearAlgebra\Vector;
  6. use MathPHP\NumberTheory\Integer;
  7. use MathPHP\Tests;
  8. /**
  9. * Tests of Matrix axioms
  10. * These tests don't test specific functions,
  11. * but rather matrix axioms which in term make use of multiple functions.
  12. * If all the Matrix math is implemented properly, these tests should
  13. * all work out according to the axioms.
  14. *
  15. * Axioms tested:
  16. * - Addition
  17. * - r(A + B) = rA + rB
  18. * - A + (−A) = 0
  19. * - Multiplication
  20. * - (AB)C = A(BC)
  21. * - A(B + C) = AB + BC
  22. * - r(AB) = (rA)B = A(rB)
  23. * - Identity
  24. * - AI = A = IA
  25. * - I is involutory
  26. * - Inverse
  27. * - AA⁻¹ = I = A⁻¹A
  28. * - (A⁻¹)⁻¹ = A
  29. * - (AB)⁻¹ = B⁻¹A⁻¹
  30. * - A is invertible, Aᵀ is inveritble
  31. * - A is invertible, AAᵀ is inveritble
  32. * - A is invertible, AᵀA is inveritble
  33. * - Transpose
  34. * - (Aᵀ)ᵀ = A
  35. * - (A⁻¹)ᵀ = (Aᵀ)⁻¹
  36. * - (rA)ᵀ = rAᵀ
  37. * - (AB)ᵀ = BᵀAᵀ
  38. * - (A + B)ᵀ = Aᵀ + Bᵀ
  39. * - Trace
  40. * - tr(A) = tr(Aᵀ)
  41. * - tr(AB) = tr(BA)
  42. * - Determinant
  43. * - det(A) = det(Aᵀ)
  44. * - det(AB) = det(A)det(B)
  45. * - LU Decomposition (PA = LU)
  46. * - PA = LU
  47. * - A = P⁻¹LU
  48. * - PPᵀ = I = PᵀP
  49. * - (PA)⁻¹ = (LU)⁻¹ = U⁻¹L⁻¹
  50. * - P⁻¹ = Pᵀ
  51. * - QR Decomposition (A = QR)
  52. * - A = QR
  53. * - Q is orthogonal, R is upper triangular
  54. * - QᵀQ = I
  55. * - R = QᵀA
  56. * - Qᵀ = Q⁻¹
  57. * - Crout Decomposition (A = LU)
  58. * - A = LU where L = LD
  59. * - Cholesky Decomposition (A = LLᵀ)
  60. * - A = LLᵀ
  61. * - System of linear equations (Ax = b)
  62. * - Ax - b = 0
  63. * - x = A⁻¹b
  64. * - LU: Ly = Pb and Ux = y
  65. * - QR: x = R⁻¹Qᵀb
  66. * - RREF of A augmented with B provides a solution to Ax = b
  67. * - Symmetric matrix
  68. * - A is square
  69. * - A = Aᵀ
  70. * - A⁻¹Aᵀ = I
  71. * - A + B is symmetric
  72. * - A - B is symmetric
  73. * - kA is symmetric
  74. * - AAᵀ is symmetric
  75. * - AᵀA is symmetric
  76. * - A is invertible symmetric, A⁻¹ is symmetric
  77. * - Skew-symmetric matrix
  78. * - The sum of two skew-symmetric matrices is skew-symmetric
  79. * - Scalar multiple of a skew-symmetric matrix is skew-symmetric
  80. * - The elements on the diagonal of a skew-symmetric matrix are zero, and therefore also its trace
  81. * - A is skew-symmetric, then det(A) ≥ 0
  82. * - A is a real skew-symmetric matrix, then I+A is invertible, where I is the identity matrix
  83. * - Kronecker product
  84. * - A ⊗ (B + C) = A ⊗ B + A ⊗ C
  85. * - (A + B) ⊗ C = A ⊗ C + B ⊗ C
  86. * - (A ⊗ B) ⊗ C = A ⊗ (B ⊗ C)
  87. * - (A ⊗ B)(C ⊗ D) = AC ⊗ BD
  88. * - (kA) ⊗ B = A ⊗ (kB) = k(A ⊗ B)
  89. * - (A ⊗ B)⁻¹ = A⁻¹ ⊗ B⁻¹
  90. * - (A ⊗ B)ᵀ = Aᵀ ⊗ Bᵀ
  91. * - det(A ⊗ B) = det(A)ᵐ det(B)ⁿ
  92. * - Kronecker sum
  93. * - A⊕B = A⊗Ib + I⊗aB
  94. * - Covariance matrix
  95. * - S = Sᵀ
  96. * - Positive definiteness
  97. * - A is PD ⇔ -A is ND
  98. * - A is PSD ⇔ -A is NSD
  99. * - A is PD ⇒ A is PSD
  100. * - A is ND ⇒ A is NSD
  101. * - A is PD ⇔ A⁻¹ is PD
  102. * - A is ND ⇔ A⁻¹ is ND
  103. * - A is PD and r > 0 ⇒ rA is PD
  104. * - A and B are PD ⇒ A + B is PD
  105. * - A and B are PD ⇒ ABA is PD
  106. * - A and B are PD ⇒ BAB is PD
  107. * - Triangular
  108. * - Zero matrix is lower triangular
  109. * - Zero matrix is upper triangular
  110. * - Lᵀ is upper triangular
  111. * - Uᵀ is lower triangular
  112. * - LL is lower triangular
  113. * - UU is upper triangular
  114. * - L + L is lower triangular
  115. * - U + U is upper triangular
  116. * - L⁻¹ is lower triangular (If L is invertible)
  117. * - U⁻¹ is upper triangular (If U is invertible)
  118. * - kL is lower triangular
  119. * - kU is upper triangular
  120. * - L is invertible iif diagonal is all non zero
  121. * - U is invertible iif diagonal is all non zero
  122. * - Diagonal
  123. * - Zero matrix is diagonal
  124. * - Dᵀ is diagonal
  125. * - DD is diagonal
  126. * - D + D is diagonal
  127. * - D⁻¹ is diagonal (If D is invertible)
  128. * - kD is lower triangular
  129. * - D is invertible iif diagonal is all non zero
  130. * - Reduced row echelon form
  131. * - RREF is upper triangular
  132. * - Exchange matrix
  133. * - Jᵀ = J
  134. * - J⁻¹ = J
  135. * - tr(J) is 1 if n is odd, and 0 if n is even
  136. * - Signature matrix
  137. * - A is involutory
  138. * - Hilbert matrix
  139. * - H is symmetric
  140. * - H is positive definite
  141. * - Cholesky decomposition
  142. * - A = LLᵀ
  143. * - L is lower triangular
  144. * - Lᵀ is upper triangular
  145. * - Adjugate matrix
  146. * - adj⟮A⟯ = Cᵀ
  147. * - A adj⟮A⟯ = det⟮A⟯ I
  148. * - A⁻¹ = (1/det⟮A⟯) adj⟮A⟯
  149. * - adj⟮I⟯ = I
  150. * - adj⟮AB⟯ = adj⟮B⟯adj⟮A⟯
  151. * - adj⟮cA⟯ = cⁿ⁻¹ adj⟮A⟯
  152. * - adj⟮B⟯adj⟮A⟯ = det⟮B⟯B⁻¹ det⟮A⟯A⁻¹ = det⟮AB⟯⟮AB⟯⁻¹
  153. * - adj⟮Aᵀ⟯ = adj⟮A⟯ᵀ
  154. * - Aadj⟮A⟯ = adj⟮A⟯A = det⟮A⟯I
  155. * - Rank
  156. * - rank(A) ≤ min(m, n)
  157. * - Zero matrix has rank of 0
  158. * - If A is square matrix, then it is invertible only if rank = n (full rank)
  159. * - rank(AᵀA) = rank(AAᵀ) = rank(A) = rank(Aᵀ)
  160. * - Bi/tridiagonal - Hessenberg
  161. * - Lower bidiagonal matrix is upper Hessenberg
  162. * - Upper bidiagonal matrix is lower Hessenberg
  163. * - A matrix that is both upper Hessenberg and lower Hessenberg is a tridiagonal matrix
  164. * - Orthogonal matrix
  165. * - AAᵀ = I
  166. * - AᵀA = I
  167. * - A⁻¹ = Aᵀ
  168. * - det(A) = 1
  169. * - Householder matrix transformation
  170. * - H is involutory
  171. * - H has determinant that is -1
  172. * - H has eigenvalues 1 and -1
  173. * - Nilpotent matrix
  174. * - tr(Aᵏ) = 0 for all k > 0
  175. * - det(A) = 0
  176. * - Cannot be invertible
  177. */
  178. class MatrixAxiomsTest extends \PHPUnit\Framework\TestCase
  179. {
  180. use Tests\LinearAlgebra\Fixture\MatrixDataProvider;
  181. /**
  182. * @test Axiom: r(A + B) = rA + rB
  183. * Order of scalar multiplication does not matter.
  184. *
  185. * @dataProvider dataProviderForScalarMultiplicationOrderAddition
  186. * @param array $A
  187. * @param array $B
  188. * @param int $r
  189. * @throws \Exception
  190. */
  191. public function testScalarMultiplicationOrderAddition(array $A, array $B, int $r)
  192. {
  193. // Given
  194. $A = MatrixFactory::create($A);
  195. $B = MatrixFactory::create($B);
  196. // When r(A + B)
  197. $A+B = $A->add($B);
  198. $r⟮A+B⟯ = $A+B->scalarMultiply($r);
  199. // And rA + rB
  200. $rA = $A->scalarMultiply($r);
  201. $rB = $B->scalarMultiply($r);
  202. $rA+rB = $rA->add($rB);
  203. // Then
  204. $this->assertEquals($r⟮A+B⟯->getMatrix(), $rA+rB->getMatrix());
  205. }
  206. /**
  207. * @return array
  208. */
  209. public function dataProviderForScalarMultiplicationOrderAddition(): array
  210. {
  211. return [
  212. [
  213. [
  214. [1, 5],
  215. [4, 3],
  216. ],
  217. [
  218. [5, 6],
  219. [2, 1],
  220. ], 5
  221. ],
  222. [
  223. [
  224. [3, 8, 5],
  225. [3, 6, 1],
  226. [9, 5, 8],
  227. ],
  228. [
  229. [5, 3, 8],
  230. [6, 4, 5],
  231. [1, 8, 9],
  232. ], 4
  233. ],
  234. [
  235. [
  236. [-4, -2, 9],
  237. [3, 14, -6],
  238. [3, 9, 9],
  239. ],
  240. [
  241. [8, 7, 8],
  242. [-5, 4, 1],
  243. [3, 5, 1],
  244. ], 7
  245. ],
  246. [
  247. [
  248. [4, 7, 7, 8],
  249. [3, 6, 4, 1],
  250. [-3, 6, 8, -3],
  251. [3, 2, 1, -54],
  252. ],
  253. [
  254. [3, 2, 6, 7],
  255. [4, 3, -6, 2],
  256. [12, 14, 14, -6],
  257. [4, 6, 4, -42],
  258. ], -8
  259. ],
  260. ];
  261. }
  262. /**
  263. * @test Axiom: A + (−A) = 0
  264. * Adding the negate of a matrix is a zero matrix.
  265. *
  266. * @dataProvider dataProviderForNegateAdditionZeroMatrix
  267. * @param array $A
  268. * @param array $Z
  269. * @throws \Exception
  270. */
  271. public function testAddNegateIsZeroMatrix(array $A, array $Z)
  272. {
  273. // Given
  274. $A = MatrixFactory::create($A);
  275. $Z = MatrixFactory::create($Z);
  276. // When
  277. $−A = $A->negate();
  278. // Then
  279. $this->assertEquals($Z, $A->add($−A));
  280. $this->assertEquals($Z, $−A->add($A));
  281. }
  282. /**
  283. * @return array [A, Z]
  284. */
  285. public function dataProviderForNegateAdditionZeroMatrix(): array
  286. {
  287. return [
  288. [
  289. [
  290. [0]
  291. ],
  292. [
  293. [0]
  294. ]
  295. ],
  296. [
  297. [
  298. [1]
  299. ],
  300. [
  301. [0]
  302. ]
  303. ],
  304. [
  305. [
  306. [1, 2, 3],
  307. [4, 5, 6],
  308. [7, 8, 9],
  309. ],
  310. [
  311. [0, 0, 0],
  312. [0, 0, 0],
  313. [0, 0, 0],
  314. ],
  315. ],
  316. [
  317. [
  318. [5, -4, 3, 2, -10],
  319. [5, 5, 5, -4, -4],
  320. [0, 0, -2, 4, 49],
  321. [4, 3, 0, 0, -1],
  322. ],
  323. [
  324. [0, 0, 0, 0, 0],
  325. [0, 0, 0, 0, 0],
  326. [0, 0, 0, 0, 0],
  327. [0, 0, 0, 0, 0],
  328. ]
  329. ],
  330. ];
  331. }
  332. /**
  333. * @test Axiom: (AB)C = A(BC)
  334. * Matrix multiplication is associative
  335. *
  336. * @dataProvider dataProviderForMultiplicationIsAssociative
  337. * @param array $A
  338. * @param array $B
  339. * @param array $C
  340. * @throws \Exception
  341. */
  342. public function testMultiplicationIsAssociative(array $A, array $B, array $C)
  343. {
  344. // Given
  345. $A = MatrixFactory::create($A);
  346. $B = MatrixFactory::create($B);
  347. $C = MatrixFactory::create($C);
  348. // When
  349. $⟮AB⟯ = $A->multiply($B);
  350. $⟮AB⟯C = $⟮AB⟯->multiply($C);
  351. // And
  352. $⟮BC⟯ = $B->multiply($C);
  353. $A⟮BC⟯ = $A->multiply($⟮BC⟯);
  354. // Then
  355. $this->assertEquals($⟮AB⟯C->getMatrix(), $A⟮BC⟯->getMatrix());
  356. }
  357. /**
  358. * @return array
  359. */
  360. public function dataProviderForMultiplicationIsAssociative(): array
  361. {
  362. return [
  363. [
  364. [
  365. [1, 5, 3],
  366. [3, 6, 3],
  367. [6, 7, 8],
  368. ],
  369. [
  370. [6, 9, 9],
  371. [3, 5, 1],
  372. [3, 5, 12],
  373. ],
  374. [
  375. [7, 4, 6],
  376. [2, 3, 1],
  377. [10, 12, 5],
  378. ],
  379. ],
  380. [
  381. [
  382. [12, 21, 6],
  383. [-3, 11, -6],
  384. [3, 6, -3],
  385. ],
  386. [
  387. [3, 7, 8],
  388. [4, 4, 2],
  389. [6, -4, 1],
  390. ],
  391. [
  392. [1, -1, -5],
  393. [6, 5, 19],
  394. [3, 6, -2],
  395. ],
  396. ],
  397. ];
  398. }
  399. /**
  400. * @test Axiom: A(B + C) = AB + AC
  401. * Matrix multiplication is distributive
  402. *
  403. * @dataProvider dataProviderForMultiplicationIsDistributive
  404. * @param array $A
  405. * @param array $B
  406. * @param array $C
  407. * @throws \Exception
  408. */
  409. public function testMultiplicationIsDistributive(array $A, array $B, array $C)
  410. {
  411. // Given
  412. $A = MatrixFactory::create($A);
  413. $B = MatrixFactory::create($B);
  414. $C = MatrixFactory::create($C);
  415. // When A(B + C)
  416. $⟮B+C⟯ = $B->add($C);
  417. $A⟮B+C⟯ = $A->multiply($⟮B+C⟯);
  418. // And AB + AC
  419. $AB = $A->multiply($B);
  420. $AC = $A->multiply($C);
  421. $AB+AC = $AB->add($AC);
  422. // Then
  423. $this->assertEquals($A⟮B+C⟯->getMatrix(), $AB+AC->getMatrix());
  424. }
  425. /**
  426. * @return array
  427. */
  428. public function dataProviderForMultiplicationIsDistributive(): array
  429. {
  430. return [
  431. [
  432. [
  433. [1, 2],
  434. [0, -1],
  435. ],
  436. [
  437. [0, -1],
  438. [1, 1],
  439. ],
  440. [
  441. [-2, 0],
  442. [0, 1],
  443. ],
  444. ],
  445. [
  446. [
  447. [1, 5, 3],
  448. [3, 6, 3],
  449. [6, 7, 8],
  450. ],
  451. [
  452. [6, 9, 9],
  453. [3, 5, 1],
  454. [3, 5, 12],
  455. ],
  456. [
  457. [7, 4, 6],
  458. [2, 3, 1],
  459. [10, 12, 5],
  460. ],
  461. ],
  462. [
  463. [
  464. [12, 21, 6],
  465. [-3, 11, -6],
  466. [3, 6, -3],
  467. ],
  468. [
  469. [3, 7, 8],
  470. [4, 4, 2],
  471. [6, -4, 1],
  472. ],
  473. [
  474. [1, -1, -5],
  475. [6, 5, 19],
  476. [3, 6, -2],
  477. ],
  478. ],
  479. ];
  480. }
  481. /**
  482. * @test Axiom: r(AB) = (rA)B = A(rB)
  483. * Order of scalar multiplication does not matter.
  484. *
  485. * @dataProvider dataProviderForScalarMultiplicationOrder
  486. * @param array $A
  487. * @param array $B
  488. * @param int $r
  489. * @throws \Exception
  490. */
  491. public function testScalarMultiplcationOrder(array $A, array $B, int $r)
  492. {
  493. // Given
  494. $A = MatrixFactory::create($A);
  495. $B = MatrixFactory::create($B);
  496. // When r(AB)
  497. $AB = $A->multiply($B);
  498. $r⟮AB⟯ = $AB->scalarMultiply($r);
  499. // And (rA)B
  500. $rA = $A->scalarMultiply($r);
  501. $⟮rA⟯B = $rA->multiply($B);
  502. // And A(rB)
  503. $rB = $B->scalarMultiply($r);
  504. $A⟮rB⟯ = $A->multiply($rB);
  505. // Then
  506. $this->assertEquals($r⟮AB⟯->getMatrix(), $⟮rA⟯B->getMatrix());
  507. $this->assertEquals($⟮rA⟯B->getMatrix(), $A⟮rB⟯->getMatrix());
  508. $this->assertEquals($r⟮AB⟯->getMatrix(), $A⟮rB⟯->getMatrix());
  509. }
  510. /**
  511. * @return array
  512. */
  513. public function dataProviderForScalarMultiplicationOrder(): array
  514. {
  515. return [
  516. [
  517. [
  518. [1, 5],
  519. [4, 3],
  520. ],
  521. [
  522. [5, 6],
  523. [2, 1],
  524. ], 5
  525. ],
  526. [
  527. [
  528. [3, 8, 5],
  529. [3, 6, 1],
  530. [9, 5, 8],
  531. ],
  532. [
  533. [5, 3, 8],
  534. [6, 4, 5],
  535. [1, 8, 9],
  536. ], 4
  537. ],
  538. [
  539. [
  540. [-4, -2, 9],
  541. [3, 14, -6],
  542. [3, 9, 9],
  543. ],
  544. [
  545. [8, 7, 8],
  546. [-5, 4, 1],
  547. [3, 5, 1],
  548. ], 7
  549. ],
  550. [
  551. [
  552. [4, 7, 7, 8],
  553. [3, 6, 4, 1],
  554. [-3, 6, 8, -3],
  555. [3, 2, 1, -54],
  556. ],
  557. [
  558. [3, 2, 6, 7],
  559. [4, 3, -6, 2],
  560. [12, 14, 14, -6],
  561. [4, 6, 4, -42],
  562. ], -8
  563. ],
  564. ];
  565. }
  566. /**
  567. * @test Axiom: AI = A = IA
  568. * Matrix multiplied with the identity matrix is the original matrix.
  569. *
  570. * @dataProvider dataProviderForSquareMatrix
  571. * @param array $A
  572. * @throws \Exception
  573. */
  574. public function testMatrixTimesIdentityIsOriginalMatrix(array $A)
  575. {
  576. // Given
  577. $A = MatrixFactory::create($A);
  578. $I = MatrixFactory::identity($A->getN());
  579. // When
  580. $AI = $A->multiply($I);
  581. $IA = $I->multiply($A);
  582. // Then
  583. $this->assertEquals($A->getMatrix(), $AI->getMatrix());
  584. $this->assertEquals($A->getMatrix(), $IA->getMatrix());
  585. }
  586. /**
  587. * @test Axiom: I is involutory
  588. * Identity matrix is involutory
  589. * @throws \Exception
  590. */
  591. public function testIdentityMatrixIsInvolutory()
  592. {
  593. // Given
  594. foreach (\range(1, 20) as $n) {
  595. // When
  596. $A = MatrixFactory::identity($n);
  597. // Then
  598. $this->assertTrue($A->isInvolutory());
  599. }
  600. }
  601. /**
  602. * @test Axiom: AA⁻¹ = I = A⁻¹A
  603. * Matrix multiplied with its inverse is the identity matrix.
  604. *
  605. * @dataProvider dataProviderForInverse
  606. * @param array $A
  607. * @throws \Exception
  608. */
  609. public function testMatrixTimesInverseIsIdentity(array $A)
  610. {
  611. // Given
  612. $A = MatrixFactory::create($A);
  613. // When
  614. $A⁻¹ = $A->inverse();
  615. $AA⁻¹ = $A->multiply($A⁻¹);
  616. $A⁻¹A = $A⁻¹->multiply($A);
  617. $I = MatrixFactory::identity($A->getN());
  618. // Then
  619. $this->assertEqualsWithDelta($I->getMatrix(), $AA⁻¹->getMatrix(), 0.00001);
  620. $this->assertEqualsWithDelta($I->getMatrix(), $A⁻¹A->getMatrix(), 0.00001);
  621. $this->assertEqualsWithDelta($AA⁻¹->getMatrix(), $A⁻¹A->getMatrix(), 0.00001);
  622. }
  623. /**
  624. * @test Axiom: (A⁻¹)⁻¹ = A
  625. * Inverse of inverse is the original matrix.
  626. *
  627. * @dataProvider dataProviderForSquareMatrixGreaterThanOneWithoutOddMatrices
  628. * @param array $A
  629. * @throws \Exception
  630. */
  631. public function testInverseOfInverseIsOriginalMatrix(array $A)
  632. {
  633. // Given
  634. $A = MatrixFactory::create($A);
  635. // When
  636. $⟮A⁻¹⟯⁻¹ = $A->inverse()->inverse();
  637. // Then
  638. $this->assertEqualsWithDelta($A->getMatrix(), $⟮A⁻¹⟯⁻¹->getMatrix(), 0.00001);
  639. }
  640. /**
  641. * @return array
  642. */
  643. public function dataProviderForInverse(): array
  644. {
  645. return [
  646. [
  647. [
  648. [4, 7],
  649. [2, 6],
  650. ],
  651. [
  652. [0.6, -0.7],
  653. [-0.2, 0.4],
  654. ],
  655. ],
  656. [
  657. [
  658. [4, 3],
  659. [3, 2],
  660. ],
  661. [
  662. [-2, 3],
  663. [3, -4],
  664. ],
  665. ],
  666. [
  667. [
  668. [1, 2],
  669. [3, 4],
  670. ],
  671. [
  672. [-2, 1],
  673. [3 / 2, -1 / 2],
  674. ],
  675. ],
  676. [
  677. [
  678. [3, 3.5],
  679. [3.2, 3.6],
  680. ],
  681. [
  682. [-9, 8.75],
  683. [8, -7.5],
  684. ],
  685. ],
  686. [
  687. [
  688. [1, 2, 3],
  689. [0, 4, 5],
  690. [1, 0, 6],
  691. ],
  692. [
  693. [12 / 11, -6 / 11, -1 / 11],
  694. [5 / 22, 3 / 22, -5 / 22],
  695. [-2 / 11, 1 / 11, 2 / 11],
  696. ],
  697. ],
  698. [
  699. [
  700. [7, 2, 1],
  701. [0, 3, -1],
  702. [-3, 4, -2],
  703. ],
  704. [
  705. [-2, 8, -5],
  706. [3, -11, 7],
  707. [9, -34, 21],
  708. ],
  709. ],
  710. [
  711. [
  712. [3, 6, 6, 8],
  713. [4, 5, 3, 2],
  714. [2, 2, 2, 3],
  715. [6, 8, 4, 2],
  716. ],
  717. [
  718. [-0.333, 0.667, 0.667, -0.333],
  719. [0.167, -2.333, 0.167, 1.417],
  720. [0.167, 4.667, -1.833, -2.583],
  721. [0.000, -2.000, 1.000, 1.000],
  722. ],
  723. ],
  724. [
  725. [
  726. [4, 23, 6, 4, 7],
  727. [3, 64, 23, 52, 2],
  728. [65, 45, 3, 23, 1],
  729. [2, 3, 4, 3, 9],
  730. [53, 99, 54, 32, 105],
  731. ],
  732. [
  733. [-0.142, 0.006, 0.003, -0.338, 0.038],
  734. [0.172, -0.012, 0.010, 0.275, -0.035],
  735. [-0.856, 0.082, -0.089, -2.344, 0.257],
  736. [0.164, -0.001, 0.026, 0.683, -0.070],
  737. [0.300, -0.033, 0.027, 0.909, -0.088],
  738. ],
  739. ],
  740. ];
  741. }
  742. /**
  743. * (AB)⁻¹ = B⁻¹A⁻¹
  744. * The inverse of a product is the reverse product of the inverses.
  745. *
  746. * @dataProvider dataProviderForInverseProductIsReverseProductOfInverses
  747. * @param array $A
  748. * @param array $B
  749. * @throws \Exception
  750. */
  751. public function testInverseProductIsReverseProductOfInverses(array $A, array $B)
  752. {
  753. // Given
  754. $A = MatrixFactory::create($A);
  755. $B = MatrixFactory::create($B);
  756. // When
  757. $⟮AB⟯⁻¹ = $A->multiply($B)->inverse();
  758. // And
  759. $B⁻¹ = $B->inverse();
  760. $A⁻¹ = $A->inverse();
  761. $B⁻¹A⁻¹ = $B⁻¹->multiply($A⁻¹);
  762. // Then
  763. $this->assertEqualsWithDelta($⟮AB⟯⁻¹->getMatrix(), $B⁻¹A⁻¹->getMatrix(), 0.00001);
  764. }
  765. /**
  766. * @test Axiom: A is invertible, Aᵀ is inveritble
  767. * If A is an invertible matrix, then the transpose is also inveritble
  768. * @dataProvider dataProviderForInverse
  769. * @param array $A
  770. * @throws \Exception
  771. */
  772. public function testIfMatrixIsInvertibleThenTransposeIsInvertible(array $A)
  773. {
  774. // Given
  775. $A = MatrixFactory::create($A);
  776. // When
  777. $Aᵀ = $A->transpose();
  778. // Then
  779. if ($A->isInvertible()) {
  780. $this->assertTrue($Aᵀ->isInvertible());
  781. } else {
  782. $this->assertFalse($Aᵀ->isInvertible());
  783. }
  784. }
  785. /**
  786. * @test Axiom: A is invertible, AAᵀ is inveritble
  787. * If A is an invertible matrix, then the product of A and tranpose of A is also inveritble
  788. * @dataProvider dataProviderForInverse
  789. * @param array $A
  790. * @throws \Exception
  791. */
  792. public function testIfMatrixIsInvertibleThenProductOfMatrixAndTransposeIsInvertible(array $A)
  793. {
  794. // Given
  795. $A = MatrixFactory::create($A);
  796. // When
  797. $Aᵀ = $A->transpose();
  798. $AAᵀ = $A->multiply($Aᵀ);
  799. // then
  800. if ($A->isInvertible()) {
  801. $this->assertTrue($AAᵀ->isInvertible());
  802. } else {
  803. $this->assertFalse($AAᵀ->isInvertible());
  804. }
  805. }
  806. /**
  807. * @test Axiom: A is invertible, AᵀA is inveritble
  808. * If A is an invertible matrix, then the product of transpose and A is also inveritble
  809. * @dataProvider dataProviderForInverse
  810. * @param array $A
  811. * @throws \Exception
  812. */
  813. public function testIfMatrixIsInvertibleThenProductOfTransposeAndMatrixIsInvertible(array $A)
  814. {
  815. // Given
  816. $A = MatrixFactory::create($A);
  817. // When
  818. $Aᵀ = $A->transpose();
  819. $AᵀA = $Aᵀ->multiply($A);
  820. // Then
  821. if ($A->isInvertible()) {
  822. $this->assertTrue($AᵀA->isInvertible());
  823. } else {
  824. $this->assertFalse($AᵀA->isInvertible());
  825. }
  826. }
  827. /**
  828. * @return array
  829. */
  830. public function dataProviderForInverseProductIsReverseProductOfInverses(): array
  831. {
  832. return [
  833. [
  834. [
  835. [1, 5],
  836. [4, 3],
  837. ],
  838. [
  839. [5, 6],
  840. [2, 1],
  841. ],
  842. ],
  843. [
  844. [
  845. [3, 8, 5],
  846. [3, 6, 1],
  847. [9, 5, 8],
  848. ],
  849. [
  850. [5, 3, 8],
  851. [6, 4, 5],
  852. [1, 8, 9],
  853. ],
  854. ],
  855. [
  856. [
  857. [-4, -2, 9],
  858. [3, 14, -6],
  859. [3, 9, 9],
  860. ],
  861. [
  862. [8, 7, 8],
  863. [-5, 4, 1],
  864. [3, 5, 1],
  865. ],
  866. ],
  867. [
  868. [
  869. [4, 7, 7, 8],
  870. [3, 6, 4, 1],
  871. [-3, 6, 8, -3],
  872. [3, 2, 1, -54],
  873. ],
  874. [
  875. [3, 2, 6, 7],
  876. [4, 3, -6, 2],
  877. [12, 14, 14, -6],
  878. [4, 6, 4, -42],
  879. ],
  880. ],
  881. ];
  882. }
  883. /**
  884. * @test Axiom: (Aᵀ)ᵀ = A
  885. * The transpose of the transpose is the original matrix.
  886. *
  887. * @dataProvider dataProviderForSquareMatrix
  888. * @param array $A
  889. * @throws \Exception
  890. */
  891. public function testTransposeOfTransposeIsOriginalMatrix(array $A)
  892. {
  893. // Given
  894. $A = MatrixFactory::create($A);
  895. // When
  896. $⟮A⁻ᵀ⟯ᵀ = $A->transpose()->transpose();
  897. // Then
  898. $this->assertEquals($⟮A⁻ᵀ⟯ᵀ->getMatrix(), $A->getMatrix());
  899. }
  900. /**
  901. * @test Axiom: (A⁻¹)ᵀ = (Aᵀ)⁻¹
  902. * The transpose of the inverse is the inverse of the transpose.
  903. *
  904. * @dataProvider dataProviderForSquareMatrixGreaterThanOneWithoutOddMatrices
  905. * @param array $A
  906. * @throws \Exception
  907. */
  908. public function testTransposeOfInverseIsInverseOfTranspose(array $A)
  909. {
  910. // Given
  911. $A = MatrixFactory::create($A);
  912. // When
  913. $⟮A⁻¹⟯ᵀ = $A->inverse()->transpose();
  914. $⟮Aᵀ⟯⁻¹ = $A->transpose()->inverse();
  915. // Then
  916. $this->assertEqualsWithDelta($⟮A⁻¹⟯ᵀ->getMatrix(), $⟮Aᵀ⟯⁻¹->getMatrix(), 0.00001);
  917. }
  918. /**
  919. * @test Axiom: (rA)ᵀ = rAᵀ
  920. * Scalar multiplication order does not matter for transpose
  921. *
  922. * @dataProvider dataProviderForSquareMatrix
  923. * @param array $A
  924. * @throws \Exception
  925. */
  926. public function testScalarMultiplicationOfTransposeOrder(array $A)
  927. {
  928. // Given
  929. $A = MatrixFactory::create($A);
  930. $r = 4;
  931. // When
  932. $⟮rA⟯ᵀ = $A->scalarMultiply($r)->transpose();
  933. $rAᵀ = $A->transpose()->scalarMultiply($r);
  934. // Then
  935. $this->assertEquals($⟮rA⟯ᵀ->getMatrix(), $rAᵀ->getMatrix());
  936. }
  937. /**
  938. * @test Axiom: (AB)ᵀ = BᵀAᵀ
  939. * Transpose of a product of matrices equals the product of their transposes in reverse order.
  940. *
  941. * @dataProvider dataProviderForTwoSquareMatrices
  942. * @param array $A
  943. * @param array $B
  944. * @throws \Exception
  945. */
  946. public function testTransposeProductIsProductOfTranposesInReverseOrder(array $A, array $B)
  947. {
  948. // Given
  949. $A = MatrixFactory::create($A);
  950. $B = MatrixFactory::create($B);
  951. // When (AB)ᵀ
  952. $⟮AB⟯ᵀ = $A->multiply($B)->transpose();
  953. // And BᵀAᵀ
  954. $Bᵀ = $B->transpose();
  955. $Aᵀ = $A->transpose();
  956. $BᵀAᵀ = $Bᵀ->multiply($Aᵀ);
  957. // Then
  958. $this->assertEquals($⟮AB⟯ᵀ->getMatrix(), $BᵀAᵀ->getMatrix());
  959. }
  960. /**
  961. * @test Axiom: (A + B)ᵀ = Aᵀ + Bᵀ
  962. * Transpose of sum is the same as sum of transposes
  963. *
  964. * @dataProvider dataProviderForTwoSquareMatrices
  965. * @param array $A
  966. * @param array $B
  967. * @throws \Exception
  968. */
  969. public function testTransposeSumIsSameAsSumOfTransposes(array $A, array $B)
  970. {
  971. // Given
  972. $A = MatrixFactory::create($A);
  973. $B = MatrixFactory::create($B);
  974. // When (A + B)ᵀ
  975. $⟮A+B⟯ᵀ = $A->add($B)->transpose();
  976. // And Aᵀ + Bᵀ
  977. $Aᵀ = $A->transpose();
  978. $Bᵀ = $B->transpose();
  979. $Aᵀ+Bᵀ = $Aᵀ->add($Bᵀ);
  980. // Then
  981. $this->assertEquals($⟮A+B⟯ᵀ->getMatrix(), $Aᵀ+Bᵀ->getMatrix());
  982. }
  983. /**
  984. * @test Axiom: tr(A) = tr(Aᵀ)
  985. * Trace is the same as the trace of the transpose
  986. *
  987. * @dataProvider dataProviderForSquareMatrix
  988. * @param array $A
  989. * @throws \Exception
  990. */
  991. public function testTraceIsSameAsTraceOfTranspose(array $A)
  992. {
  993. // Given
  994. $A = MatrixFactory::create($A);
  995. // When
  996. $Aᵀ = $A->transpose();
  997. $tr⟮A⟯ = $A->trace();
  998. $tr⟮Aᵀ⟯ = $Aᵀ->trace();
  999. // Then
  1000. $this->assertEquals($tr⟮A⟯, $tr⟮Aᵀ⟯);
  1001. }
  1002. /**
  1003. * @test Axiom: tr(AB) = tr(BA)
  1004. * Trace of product does not matter the order they were multiplied
  1005. *
  1006. * @dataProvider dataProviderForTwoSquareMatrices
  1007. * @param array $A
  1008. * @param array $B
  1009. * @throws \Exception
  1010. */
  1011. public function testTraceOfProductIsSameRegardlessOfOrderMultiplied(array $A, array $B)
  1012. {
  1013. // Given
  1014. $A = MatrixFactory::create($A);
  1015. $B = MatrixFactory::create($B);
  1016. // When
  1017. $tr⟮AB⟯ = $A->multiply($B)->trace();
  1018. $tr⟮BA⟯ = $B->multiply($A)->trace();
  1019. // Then
  1020. $this->assertEquals($tr⟮AB⟯, $tr⟮BA⟯);
  1021. }
  1022. /**
  1023. * @test Axiom: det(A) = det(Aᵀ)
  1024. * Determinant of matrix is the same as determinant of transpose.
  1025. *
  1026. * @dataProvider dataProviderForSquareMatrix
  1027. * @param array $A
  1028. * @throws \Exception
  1029. */
  1030. public function testDeterminantSameAsDeterminantOfTranspose(array $A)
  1031. {
  1032. // Given
  1033. $A = MatrixFactory::create($A);
  1034. // When
  1035. $det⟮A⟯ = $A->det();
  1036. $det⟮Aᵀ⟯ = $A->transpose()->det();
  1037. // Then
  1038. $this->assertEqualsWithDelta($det⟮A⟯, $det⟮Aᵀ⟯, 0.00001);
  1039. }
  1040. /**
  1041. * @test Axiom: det(AB) = det(A)det(B)
  1042. * Determinant of product of matrices is the same as the product of determinants.
  1043. *
  1044. * @dataProvider dataProviderForTwoSquareMatrices
  1045. * @param array $A
  1046. * @param array $B
  1047. * @throws \Exception
  1048. */
  1049. public function testDeterminantProductSameAsProductOfDeterminants(array $A, array $B)
  1050. {
  1051. // Given
  1052. $A = MatrixFactory::create($A);
  1053. $B = MatrixFactory::create($B);
  1054. // When det(AB)
  1055. $det⟮AB⟯ = $A->multiply($B)->det();
  1056. // And det(A)det(B)
  1057. $det⟮A⟯ = $A->det();
  1058. $det⟮B⟯ = $B->det();
  1059. $det⟮A⟯det⟮B⟯ = $det⟮A⟯ * $det⟮B⟯;
  1060. // Then
  1061. $this->assertEqualsWithDelta($det⟮AB⟯, $det⟮A⟯det⟮B⟯, 0.000001);
  1062. }
  1063. /**
  1064. * @test Axiom: PA = LU
  1065. * Basic LU decomposition property that permutation matrix times the matrix is the product of the lower and upper decomposition matrices.
  1066. * @dataProvider dataProviderForSquareMatrixGreaterThanOneWithoutOddMatrices
  1067. * @dataProvider dataProviderForSymmetricMatrix
  1068. * @param array $A
  1069. * @throws \Exception
  1070. */
  1071. public function testLUDecompositionPAEqualsLU(array $A)
  1072. {
  1073. // Given
  1074. $A = MatrixFactory::create($A);
  1075. // When
  1076. $LUP = $A->luDecomposition();
  1077. $L = $LUP->L;
  1078. $U = $LUP->U;
  1079. $P = $LUP->P;
  1080. // Then PA = LU;
  1081. $PA = $P->multiply($A);
  1082. $LU = $L->multiply($U);
  1083. $this->assertEqualsWithDelta($PA->getMatrix(), $LU->getMatrix(), 0.00001);
  1084. }
  1085. /**
  1086. * @test Axiom: A = P⁻¹LU
  1087. * @dataProvider dataProviderForSquareMatrixGreaterThanOneWithoutOddMatrices
  1088. * @dataProvider dataProviderForSymmetricMatrix
  1089. * @param array $A
  1090. * @throws \Exception
  1091. */
  1092. public function testLUDecompositionAEqualsPInverseLU(array $A)
  1093. {
  1094. // Given
  1095. $A = MatrixFactory::create($A);
  1096. // When
  1097. $LUP = $A->luDecomposition();
  1098. $L = $LUP->L;
  1099. $U = $LUP->U;
  1100. $P = $LUP->P;
  1101. // Then A = P⁻¹LU
  1102. $P⁻¹LU = $P->inverse()->multiply($L)->multiply($U);
  1103. $this->assertEqualsWithDelta($A->getMatrix(), $P⁻¹LU->getMatrix(), 0.00001);
  1104. }
  1105. /**
  1106. * @test Axiom: PPᵀ = I = PᵀP
  1107. * Permutation matrix of the LU decomposition times the transpose of the permutation matrix is the identity matrix.
  1108. * @dataProvider dataProviderForSquareMatrix
  1109. * @dataProvider dataProviderForSymmetricMatrix
  1110. * @param array $A
  1111. * @throws \Exception
  1112. */
  1113. public function testLUDecompositionPPTransposeEqualsIdentity(array $A)
  1114. {
  1115. // Given
  1116. $A = MatrixFactory::create($A);
  1117. // When
  1118. $LUP = $A->luDecomposition();
  1119. $P = $LUP->P;
  1120. $Pᵀ = $P->transpose();
  1121. $PPᵀ = $P->multiply($Pᵀ);
  1122. $PᵀP = $Pᵀ->multiply($P);
  1123. $I = MatrixFactory::identity($A->getM());
  1124. // Then PPᵀ = I = PᵀP
  1125. $this->assertEquals($PPᵀ->getMatrix(), $I->getMatrix());
  1126. $this->assertEquals($I->getMatrix(), $PᵀP->getMatrix());
  1127. $this->assertEquals($PPᵀ->getMatrix(), $PᵀP->getMatrix());
  1128. }
  1129. /**
  1130. * @test Axiom: (PA)⁻¹ = (LU)⁻¹ = U⁻¹L⁻¹
  1131. * Inverse of the LU decomposition equation is the inverse of the other side.
  1132. * @dataProvider dataProviderForSquareMatrixGreaterThanOneWithoutOddMatrices
  1133. * @dataProvider dataProviderForSymmetricMatrix
  1134. * @param array $A
  1135. * @throws \Exception
  1136. */
  1137. public function testInverseWithLUDecompositionInverse(array $A)
  1138. {
  1139. // Given
  1140. $A = MatrixFactory::create($A);
  1141. // When
  1142. $LUP = $A->luDecomposition();
  1143. $L = $LUP->L;
  1144. $U = $LUP->U;
  1145. $P = $LUP->P;
  1146. $⟮PA⟯⁻¹ = $P->multiply($A)->inverse();
  1147. $⟮LU⟯⁻¹ = $L->multiply($U)->inverse();
  1148. $U⁻¹ = $U->inverse();
  1149. $L⁻¹ = $L->inverse();
  1150. $U⁻¹L⁻¹ = $U⁻¹->multiply($L⁻¹);
  1151. // Then (PA)⁻¹ = (LU)⁻¹ = U⁻¹L⁻¹
  1152. $this->assertEqualsWithDelta($⟮PA⟯⁻¹->getMatrix(), $⟮LU⟯⁻¹->getMatrix(), 0.00001);
  1153. $this->assertEqualsWithDelta($⟮LU⟯⁻¹->getMatrix(), $U⁻¹L⁻¹->getMatrix(), 0.00001);
  1154. $this->assertEqualsWithDelta($⟮PA⟯⁻¹->getMatrix(), $U⁻¹L⁻¹->getMatrix(), 0.00001);
  1155. }
  1156. /**
  1157. * @test Axiom: A = QR
  1158. * Basic QR decomposition property that A = QR
  1159. * @dataProvider dataProviderForSquareMatrix
  1160. * @dataProvider dataProviderForNotSquareMatrix
  1161. * @dataProvider dataProviderForSymmetricMatrix
  1162. * @dataProvider dataProviderForNotSymmetricMatrix
  1163. * @param array $A
  1164. * @throws \Exception
  1165. */
  1166. public function testQRDecompositionAEqualsQR(array $A)
  1167. {
  1168. // Given
  1169. $A = MatrixFactory::create($A);
  1170. // When
  1171. $qr = $A->qrDecomposition();
  1172. $Q = $qr->Q;
  1173. $R = $qr->R;
  1174. // Then A = QR
  1175. $this->assertEqualsWithDelta($A->getMatrix(), $Q->multiply($R)->getMatrix(), 0.00001);
  1176. }
  1177. /**
  1178. * @test Axiom: QR.Q is orthogonal and QR.R is upper triangular
  1179. * QR decomposition properties Q is orthogonal and R is upper triangular
  1180. * @dataProvider dataProviderForSquareMatrix
  1181. * @dataProvider dataProviderForSymmetricMatrix
  1182. * @param array $A
  1183. * @throws \Exception
  1184. */
  1185. public function testQRDecompositionQOrthogonalRUpperTriangular(array $A)
  1186. {
  1187. // Given
  1188. $A = MatrixFactory::create($A);
  1189. // When
  1190. $qr = $A->qrDecomposition();
  1191. // Then Q is orthogonal and R is upper triangular
  1192. $this->assertTrue($qr->Q->isOrthogonal());
  1193. $this->assertTrue($qr->R->isUpperTriangular());
  1194. }
  1195. /**
  1196. * @test Axiom QᵀQ = I
  1197. * QR decomposition property orthonormal matrix Q has the property QᵀQ = I
  1198. * @dataProvider dataProviderForSquareMatrix
  1199. * @dataProvider dataProviderForNotSquareMatrix
  1200. * @dataProvider dataProviderForSymmetricMatrix
  1201. * @dataProvider dataProviderForNotSymmetricMatrix
  1202. * @param array $A
  1203. * @throws \Exception
  1204. */
  1205. public function testQrDecompositionOrthonormalMatrixQPropertyQTransposeQIsIdentity(array $A)
  1206. {
  1207. // Given
  1208. $A = MatrixFactory::create($A);
  1209. $I = MatrixFactory::identity(\min($A->getM(), $A->getN()));
  1210. // And
  1211. $qr = $A->qrDecomposition();
  1212. // When
  1213. $QᵀQ = $qr->Q->transpose()->multiply($qr->Q);
  1214. // Then QᵀQ = I
  1215. $this->assertEqualsWithDelta($I->getMatrix(), $QᵀQ->getMatrix(), 0.000001);
  1216. }
  1217. /**
  1218. * @test Axiom R = QᵀA
  1219. * QR decomposition property R = QᵀA
  1220. * @dataProvider dataProviderForSquareMatrix
  1221. * @dataProvider dataProviderForNotSquareMatrix
  1222. * @dataProvider dataProviderForSymmetricMatrix
  1223. * @dataProvider dataProviderForNotSymmetricMatrix
  1224. * @param array $A
  1225. * @throws \Exception
  1226. */
  1227. public function testQrDecompositionPropertyREqualsQTransposeA(array $A)
  1228. {
  1229. // Given
  1230. $A = MatrixFactory::create($A);
  1231. // And
  1232. $qrDecomposition = $A->qrDecomposition();
  1233. // When
  1234. $QᵀA = $qrDecomposition->Q->transpose()->multiply($A);
  1235. // Then R = QᵀA
  1236. $this->assertEqualsWithDelta($qrDecomposition->R->getMatrix(), $QᵀA->getMatrix(), 0.00001);
  1237. }
  1238. /**
  1239. * @test Axiom Qᵀ = Q⁻¹
  1240. * QR decomposition property Qᵀ = Q⁻¹
  1241. * @dataProvider dataProviderForSquareMatrix
  1242. * @dataProvider dataProviderForSymmetricMatrix
  1243. * @param array $A
  1244. * @throws \Exception
  1245. */
  1246. public function testQrDecompositionPropertyQTransposeEqualsQInverse(array $A)
  1247. {
  1248. // Given
  1249. $A = MatrixFactory::create($A);
  1250. // And
  1251. $Q = $A->qrDecomposition()->Q;
  1252. // When
  1253. $Qᵀ = $Q->transpose();
  1254. $Q⁻¹ = $Q->inverse();
  1255. // Then Qᵀ = Q⁻¹
  1256. $this->assertEqualsWithDelta($Qᵀ->getMatrix(), $Q⁻¹->getMatrix(), 0.00001);
  1257. }
  1258. /**
  1259. * @test Axiom: A = LU where L = LD
  1260. * Basic Crout decomposition property that A = (LD)U
  1261. * @dataProvider dataProviderForSquareMatrixGreaterThanOneWithoutOddMatrices
  1262. * @dataProvider dataProviderForSymmetricMatrix
  1263. * @param array $A
  1264. * @throws \Exception
  1265. */
  1266. public function testCroutDecompositionAEqualsLDU(array $A)
  1267. {
  1268. // Given
  1269. $A = MatrixFactory::create($A);
  1270. // When
  1271. $crout = $A->croutDecomposition();
  1272. $L = $crout->L;
  1273. $U = $crout->U;
  1274. // Then A = LU
  1275. $this->assertEqualsWithDelta($A->getMatrix(), $L->multiply($U)->getMatrix(), 0.00001);
  1276. }
  1277. /**
  1278. * @test Axiom: A = LLᵀ
  1279. * Basic Cholesky decomposition property that A = LLᵀ
  1280. * @dataProvider dataProviderForPositiveDefiniteMatrix
  1281. * @param array $A
  1282. * @throws \Exception
  1283. */
  1284. public function testCholeskyDecompositionAEqualsLLᵀ(array $A)
  1285. {
  1286. // Given
  1287. $A = MatrixFactory::create($A);
  1288. // When
  1289. $cholesky = $A->choleskyDecomposition();
  1290. $L = $cholesky->L;
  1291. $Lᵀ = $cholesky->LT;
  1292. // Then A = LLᵀ
  1293. $this->assertEqualsWithDelta($A->getMatrix(), $L->multiply($Lᵀ)->getMatrix(), 0.00001);
  1294. }
  1295. /**
  1296. * @test Axiom: P⁻¹ = Pᵀ
  1297. * Inverse of the permutation matrix equals the transpose of the permutation matrix
  1298. *
  1299. * @dataProvider dataProviderForSquareMatrix
  1300. * @param array $A
  1301. * @throws \Exception
  1302. */
  1303. public function testPInverseEqualsPTranspose(array $A)
  1304. {
  1305. // Given
  1306. $A = MatrixFactory::create($A);
  1307. // When
  1308. $LUP = $A->luDecomposition();
  1309. $P = $LUP->P;
  1310. $P⁻¹ = $P->inverse();
  1311. $Pᵀ = $P->transpose();
  1312. // Then
  1313. $this->assertEquals($P⁻¹, $Pᵀ);
  1314. }
  1315. /**
  1316. * @test Axiom: Ax - b = 0
  1317. * Matrix multiplied with unknown x vector subtract solution b is 0
  1318. *
  1319. * @dataProvider dataProviderForSolve
  1320. * @param array $A
  1321. * @param array $b
  1322. * @param array $x
  1323. * @throws \Exception
  1324. */
  1325. public function testSolveEquationForZero(array $A, array $b, array $x)
  1326. {
  1327. // Given
  1328. $A = MatrixFactory::create($A);
  1329. $x = new Vector($x);
  1330. $b = (new Vector($b))->asColumnMatrix();
  1331. // And zeros
  1332. $z = (new Vector(\array_fill(0, count($x), 0)))->asColumnMatrix();
  1333. // When Ax - b
  1334. $R = $A->multiply($x)->subtract($b);
  1335. // Then
  1336. $this->assertEqualsWithDelta($z, $R, 0.01);
  1337. }
  1338. /**
  1339. * @test Axiom: x = A⁻¹b
  1340. * Inverse of A multiplied with b is a solution to x
  1341. *
  1342. * @dataProvider dataProviderForSolve
  1343. * @param array $A
  1344. * @param array $b
  1345. * @param array $x
  1346. * @throws \Exception
  1347. */
  1348. public function testSolveInverseBEqualsX(array $A, array $b, array $x)
  1349. {
  1350. // Given
  1351. $A = MatrixFactory::create($A);
  1352. $A⁻¹ = $A->inverse();
  1353. $x = (new Vector($x))->asColumnMatrix();
  1354. $b = new Vector($b);
  1355. // When A⁻¹b
  1356. $A⁻¹b = $A⁻¹->multiply($b);
  1357. // Then
  1358. $this->assertEqualsWithDelta($x, $A⁻¹b, 0.001);
  1359. }
  1360. /**
  1361. * @test Axiom: LU: Ly = Pb and Ux = y
  1362. * LU decomposition provides a solution to Ax = b
  1363. *
  1364. * @dataProvider dataProviderForSolve
  1365. * @param array $A
  1366. * @param array $b
  1367. * @param array $expected
  1368. * @throws \Exception
  1369. */
  1370. public function testSolveLuDecomposition(array $A, array $b, array $expected)
  1371. {
  1372. // Given
  1373. $A = MatrixFactory::create($A);
  1374. $expected = new Vector($expected);
  1375. // And
  1376. $LU = $A->luDecomposition();
  1377. // When Ly = Pb and Ux = y
  1378. $x = $LU->solve($b);
  1379. // Then
  1380. $this->assertEqualsWithDelta($expected, $x, 0.001);
  1381. }
  1382. /**
  1383. * @test Axiom: QR: x = R⁻¹Qᵀb
  1384. * QR decomposition provides a solution to Ax = b
  1385. *
  1386. * @dataProvider dataProviderForSolve
  1387. * @param array $A
  1388. * @param array $b
  1389. * @param array $expected
  1390. * @throws \Exception
  1391. */
  1392. public function testSolveQrDecomposition(array $A, array $b, array $expected)
  1393. {
  1394. // Given
  1395. $A = MatrixFactory::create($A);
  1396. $expected = new Vector($expected);
  1397. // And
  1398. $QR = $A->qrDecomposition();
  1399. // When x = R⁻¹Qᵀb
  1400. $x = $QR->solve($b);
  1401. // Then
  1402. $this->assertEqualsWithDelta($expected, $x, 0.001);
  1403. }
  1404. /**
  1405. * @test Axiom: RREF of A augmented with B provides a solution to Ax = b
  1406. *
  1407. * @dataProvider dataProviderForSolve
  1408. * @param array $A
  1409. * @param array $b
  1410. * @param array $expected
  1411. * @throws \Exception
  1412. */
  1413. public function testSolveRref(array $A, array $b, array $expected)
  1414. {
  1415. // Given
  1416. $A = MatrixFactory::create($A);
  1417. $b = new Vector($b);
  1418. $expected = new Vector($expected);
  1419. // And
  1420. $QR = $A->qrDecomposition();
  1421. // When RREF
  1422. $Ab = $A->augment($b->asColumnMatrix());
  1423. $rref = $Ab->rref();
  1424. $x = new Vector(\array_column($rref->getMatrix(), $rref->getN() - 1));
  1425. // Then
  1426. $this->assertEqualsWithDelta($expected, $x, 0.001);
  1427. }
  1428. /**
  1429. * @test Axiom: Symmetric matrix is square
  1430. * @dataProvider dataProviderForSymmetricMatrix
  1431. * @param array $A
  1432. * @throws \Exception
  1433. */
  1434. public function testSymmetricMatrixIsSquare(array $A)
  1435. {
  1436. // Given
  1437. $A = MatrixFactory::create($A);
  1438. // When
  1439. $isSquare = $A->isSquare();
  1440. // Then
  1441. $this->assertTrue($isSquare);
  1442. }
  1443. /**
  1444. * @test Axiom: A = Aᵀ
  1445. * Symmetric matrix is the same as its transpose
  1446. * @dataProvider dataProviderForSymmetricMatrix
  1447. * @param array $A
  1448. * @throws \Exception
  1449. */
  1450. public function testSymmetricEqualsTranspose(array $A)
  1451. {
  1452. // Given
  1453. $A = MatrixFactory::create($A);
  1454. // When
  1455. $Aᵀ = $A->transpose();
  1456. // Then
  1457. $this->assertEquals($A->getMatrix(), $Aᵀ->getMatrix());
  1458. }
  1459. /**
  1460. * @test Axiom: A⁻¹Aᵀ = I
  1461. * Symmetric matrix inverse times transpose equals identity matrix
  1462. * @dataProvider dataProviderForSymmetricMatrix
  1463. * @param array $A
  1464. * @throws \Exception
  1465. */
  1466. public function testSymmetricInverseTransposeEqualsIdentity(array $A)
  1467. {
  1468. // Given
  1469. $A = MatrixFactory::create($A);
  1470. // When
  1471. $A⁻¹ = $A->inverse();
  1472. $Aᵀ = $A->transpose();
  1473. // And
  1474. $A⁻¹Aᵀ = $A⁻¹->multiply($Aᵀ);
  1475. $I = MatrixFactory::identity($A->getM());
  1476. // Then
  1477. $this->assertEqualsWithDelta($I, $A⁻¹Aᵀ, 0.00001);
  1478. $this->assertEqualsWithDelta($I->getMatrix(), $A⁻¹Aᵀ->getMatrix(), 0.00001);
  1479. }
  1480. /**
  1481. * @test Axiom: A + B is symmetric
  1482. * If A and B are symmetric matrices with the sme size, then A + B is symmetric
  1483. * @dataProvider dataProviderForSymmetricMatrix
  1484. * @param array $M
  1485. * @throws \Exception
  1486. */
  1487. public function testSymmetricMatricesSumIsSymmetric(array $M)
  1488. {
  1489. // Given
  1490. $A = MatrixFactory::create($M);
  1491. $B = MatrixFactory::create($M);
  1492. // When
  1493. $A+B = $A->add($B);
  1494. // Then
  1495. $this->assertTrue($A->isSymmetric());
  1496. $this->assertTrue($B->isSymmetric());
  1497. $this->assertTrue($A+B->isSymmetric());
  1498. }
  1499. /**
  1500. * @test Axiom: A - B is symmetric
  1501. * If A and B are symmetric matrices with the sme size, then A - B is symmetric
  1502. * @dataProvider dataProviderForSymmetricMatrix
  1503. * @param array $M
  1504. * @throws \Exception
  1505. */
  1506. public function testSymmetricMatricesDifferenceIsSymmetric(array $M)
  1507. {
  1508. // Given
  1509. $A = MatrixFactory::create($M);
  1510. $B = MatrixFactory::create($M);
  1511. // When
  1512. $A−B = $A->subtract($B);
  1513. // Then
  1514. $this->assertTrue($A->isSymmetric());
  1515. $this->assertTrue($B->isSymmetric());
  1516. $this->assertTrue($A−B->isSymmetric());
  1517. }
  1518. /**
  1519. * @test Axiom: kA is symmetric
  1520. * If A is a symmetric matrix, kA is symmetric
  1521. * @dataProvider dataProviderForSymmetricMatrix
  1522. * @param array $M
  1523. * @throws \Exception
  1524. */
  1525. public function testSymmetricMatricesTimesScalarIsSymmetric(array $M)
  1526. {
  1527. // Given
  1528. $A = MatrixFactory::create($M);
  1529. $this->assertTrue($A->isSymmetric());
  1530. foreach (\range(1, 10) as $k) {
  1531. // When
  1532. $kA = $A->scalarMultiply($k);
  1533. // Then
  1534. $this->assertTrue($kA->isSymmetric());
  1535. }
  1536. }
  1537. /**
  1538. * @test Axiom: AAᵀ is symmetric
  1539. * If A is a symmetric matrix, AAᵀ is symmetric
  1540. * @dataProvider dataProviderForSymmetricMatrix
  1541. * @param array $M
  1542. * @throws \Exception
  1543. */
  1544. public function testSymmetricMatrixTimesTransposeIsSymmetric(array $M)
  1545. {
  1546. // Given
  1547. $A = MatrixFactory::create($M);
  1548. // When
  1549. $Aᵀ = $A->transpose();
  1550. $AAᵀ = $A->multiply($Aᵀ);
  1551. // Then
  1552. $this->assertTrue($A->isSymmetric());
  1553. $this->assertTrue($AAᵀ->isSymmetric());
  1554. }
  1555. /**
  1556. * @test Axiom: AᵀA is symmetric
  1557. * If A is a symmetric matrix, AᵀA is symmetric
  1558. * @dataProvider dataProviderForSymmetricMatrix
  1559. * @param array $M
  1560. * @throws \Exception
  1561. */
  1562. public function testTransposeTimesSymmetricMatrixIsSymmetric(array $M)
  1563. {
  1564. // Given
  1565. $A = MatrixFactory::create($M);
  1566. // When
  1567. $Aᵀ = $A->transpose();
  1568. $AᵀA = $Aᵀ->multiply($A);
  1569. // Then
  1570. $this->assertTrue($A->isSymmetric());
  1571. $this->assertTrue($AᵀA->isSymmetric());
  1572. }
  1573. /**
  1574. * @test Axiom: A is invertible symmetric, A⁻¹ is symmetric
  1575. * If A is an invertible symmetric matrix, the inverse of A is also symmetric
  1576. * @dataProvider dataProviderForSymmetricMatrix
  1577. * @param array $M
  1578. * @throws \Exception
  1579. */
  1580. public function testMatrixIsInvertibleSymmetricThenInverseIsSymmetric(array $M)
  1581. {
  1582. // Given
  1583. $A = MatrixFactory::create($M);
  1584. if ($A->isInvertible() && $A->isSymmetric()) {
  1585. // When
  1586. $A⁻¹ = $A->inverse();
  1587. $A⁻¹ = $A⁻¹->map(
  1588. function ($x) {
  1589. return \round($x, 5); // Floating point adjustment
  1590. }
  1591. );
  1592. // Theb
  1593. $this->assertTrue($A⁻¹->isSymmetric());
  1594. }
  1595. }
  1596. /**
  1597. * @test Axiom: A is skew-symmetric, det(A) ≥ 0
  1598. * If A is a skew-symmetric matrix, the determinant is greater than zero.
  1599. * @dataProvider dataProviderForSkewSymmetricMatrix
  1600. * @param array $A
  1601. * @throws \Exception
  1602. */
  1603. public function testMatrixIsSkewSymmetricDeterminantGreaterThanZero(array $A)
  1604. {
  1605. // Given
  1606. $A = MatrixFactory::create($A);
  1607. // Then
  1608. $this->assertTrue($A->isSkewSymmetric());
  1609. $this->assertGreaterThanOrEqual(0, $A->det());
  1610. }
  1611. /**
  1612. * @test Axiom: The sum of two skew-symmetric matrices is skew-symmetric
  1613. * @dataProvider dataProviderForSkewSymmetricMatrix
  1614. * @param array $A
  1615. * @throws \Exception
  1616. */
  1617. public function testSumOfTwoSkewSymmetricMatricesIsSkewSymmetric(array $A)
  1618. {
  1619. // Given
  1620. $A = MatrixFactory::create($A);
  1621. // When
  1622. $B = $A->add($A);
  1623. // Then
  1624. $this->assertTrue($B->isSkewSymmetric());
  1625. }
  1626. /**
  1627. * @test Axiom: Scalar multiple of a skew-symmetric matrix is skew-symmetric
  1628. * @dataProvider dataProviderForSkewSymmetricMatrix
  1629. * @param array $A
  1630. * @throws \Exception
  1631. */
  1632. public function testScalarMultipleOfSkewSymmetricMatrixIsSkewSymmetric(array $A)
  1633. {
  1634. $A = MatrixFactory::create($A);
  1635. $B = $A->scalarMultiply(6);
  1636. $this->assertTrue($B->isSkewSymmetric());
  1637. }
  1638. /**
  1639. * @test Axiom: The elements on the diagonal of a skew-symmetric matrix are zero, and therefore also its trace
  1640. * @dataProvider dataProviderForSkewSymmetricMatrix
  1641. * @param array $A
  1642. * @throws \Exception
  1643. */
  1644. public function testSkewSymmetricMatrixDiagonalElementsAreZeroAndThereforeTraceIsZero(array $A)
  1645. {
  1646. // Given
  1647. $A = MatrixFactory::create($A);
  1648. // When
  1649. foreach ($A->getDiagonalElements() as $diagonal_element) {
  1650. // Then
  1651. $this->assertEquals(0, $diagonal_element);
  1652. }
  1653. // And When
  1654. $trace = $A->trace();
  1655. // Then
  1656. $this->assertEquals(0, $trace);
  1657. }
  1658. /**
  1659. * @test Axiom: A is a real skew-symmetric matrix, then I+A is invertible, where I is the identity matrix
  1660. * @dataProvider dataProviderForSkewSymmetricMatrix
  1661. * @param array $A
  1662. * @throws \Exception
  1663. */
  1664. public function testSkewSymmetricMatrixAddedToIdentityIsInvertible(array $A)
  1665. {
  1666. // Given
  1667. $A = MatrixFactory::create($A);
  1668. $I = MatrixFactory::identity($A->getN());
  1669. // When
  1670. $I+A = $I->add($A);
  1671. // Then
  1672. $this->assertTrue($I+A->isInvertible());
  1673. }
  1674. /**
  1675. * @test Axiom: A ⊗ (B + C) = A ⊗ B + A ⊗ C
  1676. * Kronecker product bilinearity
  1677. * @dataProvider dataProviderForThreeMatrices
  1678. * @param array $A
  1679. * @param array $B
  1680. * @param array $C
  1681. * @throws \Exception
  1682. */
  1683. public function testKroneckerProductBilinearity1(array $A, array $B, array $C)
  1684. {
  1685. // Given
  1686. $A = MatrixFactory::create($A);
  1687. $B = MatrixFactory::create($B);
  1688. $C = MatrixFactory::create($C);
  1689. // When
  1690. $A⊗⟮B+C⟯ = $A->kroneckerProduct($B->add($C));
  1691. $A⊗B+A⊗C = $A->kroneckerProduct($B)->add($A->kroneckerProduct($C));
  1692. // Then
  1693. $this->assertEquals($A⊗⟮B+C⟯->getMatrix(), $A⊗B+A⊗C->getMatrix());
  1694. }
  1695. /**
  1696. * @test Axiom: (A + B) ⊗ C = A ⊗ C + B ⊗ C
  1697. * Kronecker product bilinearity
  1698. * @dataProvider dataProviderForThreeMatrices
  1699. * @param array $A
  1700. * @param array $B
  1701. * @param array $C
  1702. * @throws \Exception
  1703. */
  1704. public function testKroneckerProductBilinearity2(array $A, array $B, array $C)
  1705. {
  1706. // Given
  1707. $A = MatrixFactory::create($A);
  1708. $B = MatrixFactory::create($B);
  1709. $C = MatrixFactory::create($C);
  1710. // When
  1711. $⟮A+B⟯⊗C = $A->add($B)->kroneckerProduct($C);
  1712. $A⊗C+B⊗C = $A->kroneckerProduct($C)->add($B->kroneckerProduct($C));
  1713. // Then
  1714. $this->assertEquals($⟮A+B⟯⊗C->getMatrix(), $A⊗C+B⊗C->getMatrix());
  1715. }
  1716. /**
  1717. * @test Axiom: (A ⊗ B) ⊗ C = A ⊗ (B ⊗ C)
  1718. * Kronecker product associative
  1719. * @dataProvider dataProviderForThreeMatrices
  1720. * @param array $A
  1721. * @param array $B
  1722. * @param array $C
  1723. * @throws \Exception
  1724. */
  1725. public function testKroneckerProductAssociativity(array $A, array $B, array $C)
  1726. {
  1727. // Given
  1728. $A = MatrixFactory::create($A);
  1729. $B = MatrixFactory::create($B);
  1730. $C = MatrixFactory::create($C);
  1731. // When
  1732. $⟮A⊗B⟯⊗C = $A->kroneckerProduct($B)->kroneckerProduct($C);
  1733. $A⊗⟮B⊗C⟯ = $A->kroneckerProduct($B->kroneckerProduct($C));
  1734. // Then
  1735. $this->assertEquals($⟮A⊗B⟯⊗C->getMatrix(), $A⊗⟮B⊗C⟯->getMatrix());
  1736. }
  1737. /**
  1738. * @test Axiom: (A ⊗ B)(C ⊗ D) = AC ⊗ BD
  1739. * Kronecker multiplication
  1740. * @dataProvider dataProviderForFourMatrices
  1741. * @param array $A
  1742. * @param array $B
  1743. * @param array $C
  1744. * @param array $D
  1745. * @throws \Exception
  1746. */
  1747. public function testKroneckerProductMultiplication(array $A, array $B, array $C, array $D)
  1748. {
  1749. // Given
  1750. $A = MatrixFactory::create($A);
  1751. $B = MatrixFactory::create($B);
  1752. $C = MatrixFactory::create($C);
  1753. $D = MatrixFactory::create($D);
  1754. // When
  1755. $⟮A⊗B⟯ = $A->kroneckerProduct($B);
  1756. $⟮C⊗D⟯ = $C->kroneckerProduct($D);
  1757. $⟮A⊗B⟯⟮C⊗D⟯ = $⟮A⊗B⟯->multiply($⟮C⊗D⟯);
  1758. // And
  1759. $AC = $A->multiply($C);
  1760. $BD = $B->multiply($D);
  1761. $AC⊗BD = $AC->kroneckerProduct($BD);
  1762. // Theb
  1763. $this->assertEquals($⟮A⊗B⟯⟮C⊗D⟯->getMatrix(), $AC⊗BD->getMatrix());
  1764. }
  1765. /**
  1766. * @test Axiom: (kA) ⊗ B = A ⊗ (kB) = k(A ⊗ B)
  1767. * Kronecker product scalar multiplication
  1768. * @dataProvider dataProviderForTwoSquareMatrices
  1769. * @param array $A
  1770. * @param array $B
  1771. * @throws \Exception
  1772. */
  1773. public function testKroneckerProductScalarMultiplication(array $A, array $B)
  1774. {
  1775. // Given
  1776. $A = MatrixFactory::create($A);
  1777. $B = MatrixFactory::create($B);
  1778. $k = 5;
  1779. // When
  1780. $⟮kA⟯⊗B = $A->scalarMultiply($k)->kroneckerProduct($B);
  1781. $A⊗⟮kB⟯ = $A->kroneckerProduct($B->scalarMultiply($k));
  1782. $k⟮A⊗B⟯ = $A->kroneckerProduct($B)->scalarMultiply($k);
  1783. // Then
  1784. $this->assertEquals($⟮kA⟯⊗B->getMatrix(), $A⊗⟮kB⟯->getMatrix());
  1785. $this->assertEquals($⟮kA⟯⊗B->getMatrix(), $k⟮A⊗B⟯->getMatrix());
  1786. $this->assertEquals($k⟮A⊗B⟯->getMatrix(), $A⊗⟮kB⟯->getMatrix());
  1787. }
  1788. /**
  1789. * @test Axiom: (A ⊗ B)⁻¹ = A⁻¹ ⊗ B⁻¹
  1790. * Inverse of Kronecker product
  1791. * @dataProvider dataProviderForTwoSquareMatrices
  1792. * @param array $A
  1793. * @param array $B
  1794. * @throws \Exception
  1795. */
  1796. public function testKroneckerProductInverse(array $A, array $B)
  1797. {
  1798. // Given
  1799. $A = MatrixFactory::create($A);
  1800. $B = MatrixFactory::create($B);
  1801. // When
  1802. $A⁻¹ = $A->inverse();
  1803. $B⁻¹ = $B->inverse();
  1804. $A⁻¹⊗B⁻¹ = $A⁻¹->kroneckerProduct($B⁻¹);
  1805. $⟮A⊗B⟯⁻¹ = $A->kroneckerProduct($B)->inverse();
  1806. // Then
  1807. $this->assertEqualsWithDelta($A⁻¹⊗B⁻¹->getMatrix(), $⟮A⊗B⟯⁻¹->getMatrix(), 0.00001);
  1808. }
  1809. /**
  1810. * @test Axiom: (A ⊗ B)ᵀ = Aᵀ ⊗ Bᵀ
  1811. * Transpose of Kronecker product
  1812. * @dataProvider dataProviderForTwoSquareMatrices
  1813. * @param array $A
  1814. * @param array $B
  1815. * @throws \Exception
  1816. */
  1817. public function testKroneckerProductTranspose(array $A, array $B)
  1818. {
  1819. // Given
  1820. $A = MatrixFactory::create($A);
  1821. $B = MatrixFactory::create($B);
  1822. // When
  1823. $Aᵀ = $A->transpose();
  1824. $Bᵀ = $B->transpose();
  1825. $Aᵀ⊗Bᵀ = $Aᵀ->kroneckerProduct($Bᵀ);
  1826. $⟮A⊗B⟯ᵀ = $A->kroneckerProduct($B)->transpose();
  1827. // Then
  1828. $this->assertEquals($Aᵀ⊗Bᵀ->getMatrix(), $⟮A⊗B⟯ᵀ->getMatrix());
  1829. }
  1830. /**
  1831. * @test Axiom: det(A ⊗ B) = det(A)ᵐ det(B)ⁿ
  1832. * Determinant of Kronecker product - where A is nxn matrix, and b is nxn matrix
  1833. * @dataProvider dataProviderForKroneckerProductDeterminant
  1834. * @param array $A
  1835. * @param array $B
  1836. * @throws \Exception
  1837. */
  1838. public function testKroneckerProductDeterminant(array $A, array $B)
  1839. {
  1840. // Given
  1841. $A = MatrixFactory::create($A);
  1842. $B = MatrixFactory::create($B);
  1843. // When
  1844. $det⟮A⟯ᵐ = ($A->det()) ** $B->getM();
  1845. $det⟮B⟯ⁿ = ($B->det()) ** $A->getN();
  1846. $det⟮A⊗B⟯ = $A->kroneckerProduct($B)->det();
  1847. // Then
  1848. $this->assertEqualsWithDelta($det⟮A⊗B⟯, $det⟮A⟯ᵐ * $det⟮B⟯ⁿ, 0.0001);
  1849. }
  1850. /**
  1851. * @return array
  1852. */
  1853. public function dataProviderForKroneckerProductDeterminant(): array
  1854. {
  1855. return [
  1856. [
  1857. [
  1858. [5],
  1859. ],
  1860. [
  1861. [4],
  1862. ],
  1863. ],
  1864. [
  1865. [
  1866. [5, 6],
  1867. [2, 4],
  1868. ],
  1869. [
  1870. [4, 9],
  1871. [3, 1],
  1872. ],
  1873. ],
  1874. [
  1875. [
  1876. [5, 6],
  1877. [-2, 4],
  1878. ],
  1879. [
  1880. [4, -9],
  1881. [3, 1],
  1882. ],
  1883. ],
  1884. [
  1885. [
  1886. [1, 2, 3],
  1887. [2, 4, 6],
  1888. [7, 6, 5],
  1889. ],
  1890. [
  1891. [2, 3, 4],
  1892. [3, 1, 6],
  1893. [4, 3, 3],
  1894. ],
  1895. ],
  1896. [
  1897. [
  1898. [1, -2, 3],
  1899. [2, -4, 6],
  1900. [7, -6, 5],
  1901. ],
  1902. [
  1903. [2, 3, 4],
  1904. [3, 1, 6],
  1905. [4, 3, 3],
  1906. ],
  1907. ],
  1908. [
  1909. [
  1910. [1, 2, 3, 4],
  1911. [2, 4, 6, 8],
  1912. [7, 6, 5, 4],
  1913. [1, 3, 5, 7],
  1914. ],
  1915. [
  1916. [2, 3, 4, 5],
  1917. [3, 1, 6, 3],
  1918. [4, 3, 3, 4],
  1919. [3, 3, 4, 1],
  1920. ],
  1921. ],
  1922. [
  1923. [
  1924. [-1, 2, 3, 4],
  1925. [2, 4, 6, 8],
  1926. [7, 6, 5, 4],
  1927. [1, 3, 5, 7],
  1928. ],
  1929. [
  1930. [2, 3, 4, 5],
  1931. [3, 1, 6, 3],
  1932. [4, 3, 3, -4],
  1933. [3, 3, 4, 1],
  1934. ],
  1935. ],
  1936. [
  1937. [
  1938. [1, 2, 3, 4, 5],
  1939. [2, 4, 6, 8, 10],
  1940. [7, 6, 5, 4, 5],
  1941. [1, 3, 5, 7, 9],
  1942. [1, 2, 3, 4, 5],
  1943. ],
  1944. [
  1945. [2, 3, 4, 5, 2],
  1946. [3, 1, 6, 3, 2],
  1947. [4, 3, 3, 4, 2],
  1948. [3, 3, 4, 1, 2],
  1949. [1, 1, 2, 2, 1],
  1950. ],
  1951. ],
  1952. ];
  1953. }
  1954. /**
  1955. * @test Axiom: Kronecker sum A⊕B = A⊗Ib + I⊗aB
  1956. * Kronecker sum is the matrix product of the Kronecker product of each matrix with the other matrix's identiry matrix.
  1957. * @dataProvider dataProviderForTwoSquareMatrices
  1958. * @param array $A
  1959. * @param array $B
  1960. * @throws \Exception
  1961. */
  1962. public function testKroneckerSum(array $A, array $B)
  1963. {
  1964. // Given
  1965. $A = new NumericSquareMatrix($A);
  1966. $B = new NumericSquareMatrix($B);
  1967. $A⊕B = $A->kroneckerSum($B);
  1968. // When
  1969. $In = MatrixFactory::identity($A->getN());
  1970. $Im = MatrixFactory::identity($B->getM());
  1971. $A⊗Im = $A->kroneckerProduct($Im);
  1972. $In⊗B = $In->kroneckerProduct($B);
  1973. $A⊗Im+In⊗B = $A⊗Im->add($In⊗B);
  1974. // Then
  1975. $this->assertEquals($A⊕B, $A⊗Im+In⊗B);
  1976. }
  1977. /**
  1978. * @test Axiom: Covariance matrix S = Sᵀ
  1979. * Covariance matrix is symmetric so it is the same as its transpose
  1980. * @dataProvider dataProviderForCovarianceSymmetric
  1981. * @param array $A
  1982. * @throws \Exception
  1983. */
  1984. public function testCovarianceMatrixIsSymmetric(array $A)
  1985. {
  1986. // Given
  1987. $A = MatrixFactory::create($A);
  1988. // When
  1989. $S = $A->covarianceMatrix();
  1990. $Sᵀ = $S->transpose();
  1991. // Then
  1992. $this->assertEquals($S->getMatrix(), $Sᵀ->getMatrix());
  1993. }
  1994. /**
  1995. * @return array
  1996. */
  1997. public function dataProviderForCovarianceSymmetric(): array
  1998. {
  1999. return [
  2000. [
  2001. [
  2002. [1, 4, 7, 8],
  2003. [2, 2, 8, 4],
  2004. [1, 13, 1, 5],
  2005. ],
  2006. ],
  2007. [
  2008. [
  2009. [19, 22, 6, 3, 2, 20],
  2010. [12, 6, 9, 15, 13, 5],
  2011. ],
  2012. ],
  2013. [
  2014. [
  2015. [4, 4.2, 3.9, 4.3, 4.1],
  2016. [2, 2.1, 2, 2.1, 2.2],
  2017. [.6, .59, .58, .62, .63]
  2018. ],
  2019. ],
  2020. [
  2021. [
  2022. [2.5, 0.5, 2.2, 1.9, 3.1, 2.3, 2, 1, 1.5, 1.1],
  2023. [2.4, 0.7, 2.9, 2.2, 3.0, 2.7, 1.6, 1.1, 1.6, 0.9],
  2024. ],
  2025. ],
  2026. ];
  2027. }
  2028. /**
  2029. * @test Axiom: Positive definiteness A is PD ⇔ -A is ND
  2030. * If A is positive definite, then -A is negative definite.
  2031. * @dataProvider dataProviderForPositiveDefiniteMatrix
  2032. * @param array $A
  2033. * @throws \Exception
  2034. */
  2035. public function testPositiveDefiniteNegativeisNegativeDefinite(array $A)
  2036. {
  2037. // Given
  2038. $A = MatrixFactory::create($A);
  2039. // When
  2040. $−A = $A->scalarMultiply(-1);
  2041. // Then
  2042. $this->assertTrue($A->isPositiveDefinite());
  2043. $this->assertTrue($−A->isNegativeDefinite());
  2044. }
  2045. /**
  2046. * @test Axiom: Positive semidefiniteness A is PSD ⇔ -A is NSD
  2047. * If A is positive semidefinite, then -A is negative definite.
  2048. * @dataProvider dataProviderForPositiveSemidefiniteMatrix
  2049. * @param array $A
  2050. * @throws \Exception
  2051. */
  2052. public function testPositiveSemidefiniteNegativeisNegativeSemidefinite(array $A)
  2053. {
  2054. // Given
  2055. $A = MatrixFactory::create($A);
  2056. // When
  2057. $−A = $A->scalarMultiply(-1);
  2058. // Then
  2059. $this->assertTrue($A->isPositiveSemidefinite());
  2060. $this->assertTrue($−A->isNegativeSemidefinite());
  2061. }
  2062. /**
  2063. * @test Axiom: Positive definiteness A is PD ⇒ A is PSD
  2064. * If A is positive definite, then A is also positive semidefinite.
  2065. * @dataProvider dataProviderForPositiveDefiniteMatrix
  2066. * @param array $A
  2067. * @throws \Exception
  2068. */
  2069. public function testPositiveDefiniteIsAlsoPositiveSemidefinite(array $A)
  2070. {
  2071. // Given
  2072. $A = MatrixFactory::create($A);
  2073. // Then
  2074. $this->assertTrue($A->isPositiveDefinite());
  2075. $this->assertTrue($A->isPositiveSemidefinite());
  2076. }
  2077. /**
  2078. * @test Axiom: Negative definiteness A is ND ⇒ A is NSD
  2079. * If A is negative definite, then A is also negative semidefinite.
  2080. * @dataProvider dataProviderForNegativeDefiniteMatrix
  2081. * @param array $A
  2082. * @throws \Exception
  2083. */
  2084. public function testNegativeDefiniteIsAlsoNegativeSemidefinite(array $A)
  2085. {
  2086. // Given
  2087. $A = MatrixFactory::create($A);
  2088. // Then
  2089. $this->assertTrue($A->isNegativeDefinite());
  2090. $this->assertTrue($A->isNegativeSemidefinite());
  2091. }
  2092. /**
  2093. * @test Axiom: Positive definiteness A is PD ⇔ A⁻¹ is PD
  2094. * If A is positive definite, then A⁻¹ is positive definite.
  2095. * @dataProvider dataProviderForPositiveDefiniteMatrix
  2096. * @param array $A
  2097. * @throws \Exception
  2098. */
  2099. public function testPositiveDefiniteInverseIsPositiveDefinite(array $A)
  2100. {
  2101. // Given
  2102. $A = MatrixFactory::create($A);
  2103. // When
  2104. $A⁻¹ = $A->inverse();
  2105. // Floating point adjustment
  2106. $A⁻¹ = $A⁻¹->map(function ($x) {
  2107. return \round($x, 7);
  2108. });
  2109. // Then
  2110. $this->assertTrue($A->isPositiveDefinite());
  2111. $this->assertTrue($A⁻¹->isPositiveDefinite());
  2112. }
  2113. /**
  2114. * @test Axiom: Negative definiteness A is ND ⇔ A⁻¹ is ND
  2115. * If A is negative definite, then A⁻¹ is negative definite.
  2116. * @dataProvider dataProviderForNegativeDefiniteMatrix
  2117. * @param array $A
  2118. * @throws \Exception
  2119. */
  2120. public function testNegativeDefiniteInverseIsNegativeDefinite(array $A)
  2121. {
  2122. // Given
  2123. $A = MatrixFactory::create($A);
  2124. // When
  2125. $A⁻¹ = $A->inverse();
  2126. // Floating point adjustment
  2127. $A⁻¹ = $A⁻¹->map(function ($x) {
  2128. return \round($x, 7);
  2129. });
  2130. // Then
  2131. $this->assertTrue($A->isNegativeDefinite());
  2132. $this->assertTrue($A⁻¹->isNegativeDefinite());
  2133. }
  2134. /**
  2135. * @test Axiom: Positive definiteness A is PD and r > 0 ⇒ rA is PD
  2136. * If A is positive definite and r > 0, then rA is positive definite.
  2137. * @dataProvider dataProviderForPositiveDefiniteMatrix
  2138. * @param array $A
  2139. * @throws \Exception
  2140. */
  2141. public function testPositiveDefiniteThenScalarMultiplyWithPositiveNumberIsPositiveDefinite(array $A)
  2142. {
  2143. // Given
  2144. $A = MatrixFactory::create($A);
  2145. $this->assertTrue($A->isPositiveDefinite());
  2146. foreach (\range(1, 10) as $r) {
  2147. // When
  2148. $rA = $A->scalarMultiply($r);
  2149. // Then
  2150. $this->assertTrue($rA->isPositiveDefinite());
  2151. }
  2152. }
  2153. /**
  2154. * @test Axiom: Positive definiteness A and B are PD ⇒ A + B is PD
  2155. * If A and B are positive definite then A + B is positive definite.
  2156. * @dataProvider dataProviderForPositiveDefiniteMatrix
  2157. * @param array $M
  2158. * @throws \Exception
  2159. */
  2160. public function testPositiveDefiniteAPlusAIsPositiveDefinite(array $M)
  2161. {
  2162. // Given
  2163. $A = MatrixFactory::create($M);
  2164. $B = MatrixFactory::create($M);
  2165. // When
  2166. $A+B = $A->add($B);
  2167. // Then
  2168. $this->assertTrue($A->isPositiveDefinite());
  2169. $this->assertTrue($B->isPositiveDefinite());
  2170. $this->assertTrue($A+B->isPositiveDefinite());
  2171. }
  2172. /**
  2173. * @test Axiom: Positive definiteness A and B are PD ⇒ A + B is PD
  2174. * If A and B are positive definite then A + B is positive definite.
  2175. * @dataProvider dataProviderForTwoPositiveDefiniteMatrices
  2176. * @param array $A
  2177. * @param array $B
  2178. * @throws \Exception
  2179. */
  2180. public function testPositiveDefiniteAPlusBIsPositiveDefinite(array $A, array $B)
  2181. {
  2182. // Given
  2183. $A = MatrixFactory::create($A);
  2184. $B = MatrixFactory::create($B);
  2185. // When
  2186. $A+B = $A->add($B);
  2187. // Then
  2188. $this->assertTrue($A->isPositiveDefinite());
  2189. $this->assertTrue($B->isPositiveDefinite());
  2190. $this->assertTrue($A+B->isPositiveDefinite());
  2191. }
  2192. /**
  2193. * @test Axiom: Positive definiteness A and B are PD ⇒ ABA is PD
  2194. * If A and B are positive definite then ABA is positive definite.
  2195. * @dataProvider dataProviderForPositiveDefiniteMatrix
  2196. * @param array $M
  2197. * @throws \Exception
  2198. */
  2199. public function testPositiveDefiniteAAAIsPositiveDefinite(array $M)
  2200. {
  2201. // Given
  2202. $A = MatrixFactory::create($M);
  2203. $B = MatrixFactory::create($M);
  2204. // When
  2205. $ABA = $A->multiply($B)->multiply($A);
  2206. // Then
  2207. $this->assertTrue($A->isPositiveDefinite());
  2208. $this->assertTrue($B->isPositiveDefinite());
  2209. $this->assertTrue($ABA->isPositiveDefinite());
  2210. }
  2211. /**
  2212. * @test Axiom: Positive definiteness A and B are PD ⇒ ABA is PD
  2213. * If A and B are positive definite then ABA is positive definite.
  2214. * @dataProvider dataProviderForTwoPositiveDefiniteMatrices
  2215. * @param array $A
  2216. * @param array $B
  2217. * @throws \Exception
  2218. */
  2219. public function testPositiveDefiniteABAIsPositiveDefinite(array $A, array $B)
  2220. {
  2221. // Given
  2222. $A = MatrixFactory::create($A);
  2223. $B = MatrixFactory::create($B);
  2224. // When
  2225. $ABA = $A->multiply($B)->multiply($A);
  2226. // Then
  2227. $this->assertTrue($A->isPositiveDefinite());
  2228. $this->assertTrue($B->isPositiveDefinite());
  2229. $this->assertTrue($ABA->isPositiveDefinite());
  2230. }
  2231. /**
  2232. * @test Axiom: Positive definiteness A and B are PD ⇒ BAB is PD
  2233. * If A and B are positive definite then BAB is positive definite.
  2234. * @dataProvider dataProviderForTwoPositiveDefiniteMatrices
  2235. * @param array $A
  2236. * @param array $B
  2237. * @throws \Exception
  2238. */
  2239. public function testPositiveDefiniteBABIsPositiveDefinite(array $A, array $B)
  2240. {
  2241. // Given
  2242. $A = MatrixFactory::create($A);
  2243. $B = MatrixFactory::create($B);
  2244. // When
  2245. $BAB = $B->multiply($A)->multiply($B);
  2246. // Then
  2247. $this->assertTrue($A->isPositiveDefinite());
  2248. $this->assertTrue($B->isPositiveDefinite());
  2249. $this->assertTrue($BAB->isPositiveDefinite());
  2250. }
  2251. /**
  2252. * @test Axiom: Zero matrix is lower triangular
  2253. * @throws \Exception
  2254. */
  2255. public function testZeroMatrixIsLowerTriangular()
  2256. {
  2257. foreach (\range(1, 20) as $m) {
  2258. // Given
  2259. $L = MatrixFactory::zero($m, $m);
  2260. // Then
  2261. $this->assertTrue($L->isLowerTriangular());
  2262. }
  2263. }
  2264. /**
  2265. * @test Axiom: Zero matrix is upper triangular
  2266. * @throws \Exception
  2267. */
  2268. public function testZeroMatrixIsUpperTriangular()
  2269. {
  2270. foreach (\range(1, 20) as $m) {
  2271. // Given
  2272. $L = MatrixFactory::zero($m, $m);
  2273. // Then
  2274. $this->assertTrue($L->isUpperTriangular());
  2275. }
  2276. }
  2277. /**
  2278. * @test Axiom: Zero matrix is diagonal
  2279. * @throws \Exception
  2280. */
  2281. public function testZeroMatrixIsDiagonal()
  2282. {
  2283. foreach (\range(1, 20) as $m) {
  2284. // Given
  2285. $L = MatrixFactory::zero($m, $m);
  2286. // Then
  2287. $this->assertTrue($L->isDiagonal());
  2288. }
  2289. }
  2290. /**
  2291. * @test Axiom: Lᵀ is upper triangular
  2292. * Transpose of a lower triangular matrix is upper triagular
  2293. * @dataProvider dataProviderForLowerTriangularMatrix
  2294. * @param array $L
  2295. * @throws \Exception
  2296. */
  2297. public function testTransposeOfLowerTriangularMatrixIsUpperTriangular(array $L)
  2298. {
  2299. // Given
  2300. $L = MatrixFactory::create($L);
  2301. // When
  2302. $Lᵀ = $L->Transpose();
  2303. // Then
  2304. $this->assertTrue($L->isLowerTriangular());
  2305. $this->assertTrue($Lᵀ->isUpperTriangular());
  2306. }
  2307. /**
  2308. * @test Axiom: Uᵀ is lower triangular
  2309. * Transpose of an upper triangular matrix is lower triagular
  2310. * @dataProvider dataProviderForUpperTriangularMatrix
  2311. * @param array $U
  2312. * @throws \Exception
  2313. */
  2314. public function testTransposeOfUpperTriangularMatrixIsLowerTriangular(array $U)
  2315. {
  2316. // Given
  2317. $U = MatrixFactory::create($U);
  2318. // When
  2319. $Uᵀ = $U->Transpose();
  2320. // Then
  2321. $this->assertTrue($U->isUpperTriangular());
  2322. $this->assertTrue($Uᵀ->isLowerTriangular());
  2323. }
  2324. /**
  2325. * @test Axiom: LL is lower triangular
  2326. * Product of two lower triangular matrices is lower triangular
  2327. * @dataProvider dataProviderForLowerTriangularMatrix
  2328. * @param array $L
  2329. * @throws \Exception
  2330. */
  2331. public function testProductOfTwoLowerTriangularMatricesIsLowerTriangular(array $L)
  2332. {
  2333. // Given
  2334. $L = MatrixFactory::create($L);
  2335. // When
  2336. $LL = $L->multiply($L);
  2337. // Then
  2338. $this->assertTrue($L->isLowerTriangular());
  2339. $this->assertTrue($LL->isLowerTriangular());
  2340. }
  2341. /**
  2342. * @test Axiom: UU is upper triangular
  2343. * Product of two upper triangular matrices is upper triangular
  2344. * @dataProvider dataProviderForUpperTriangularMatrix
  2345. * @param array $U
  2346. * @throws \Exception
  2347. */
  2348. public function testProductOfTwoUpperTriangularMatricesIsUpperTriangular(array $U)
  2349. {
  2350. // Given
  2351. $U = MatrixFactory::create($U);
  2352. // When
  2353. $UU = $U->multiply($U);
  2354. // Then
  2355. $this->assertTrue($U->isUpperTriangular());
  2356. $this->assertTrue($UU->isUpperTriangular());
  2357. }
  2358. /**
  2359. * @test Axiom: L + L is lower triangular
  2360. * Sum of two lower triangular matrices is lower triangular
  2361. * @dataProvider dataProviderForLowerTriangularMatrix
  2362. * @param array $L
  2363. * @throws \Exception
  2364. */
  2365. public function testSumOfTwoLowerTriangularMatricesIsLowerTriangular(array $L)
  2366. {
  2367. // Given
  2368. $L = MatrixFactory::create($L);
  2369. // When
  2370. $L+L = $L->add($L);
  2371. // Then
  2372. $this->assertTrue($L->isLowerTriangular());
  2373. $this->assertTrue($L+L->isLowerTriangular());
  2374. }
  2375. /**
  2376. * @test Axiom: U + U is upper triangular
  2377. * Sum of two upper triangular matrices is upper triangular
  2378. * @dataProvider dataProviderForUpperTriangularMatrix
  2379. * @param array $U
  2380. * @throws \Exception
  2381. */
  2382. public function testSumOfTwoUpperTriangularMatricesIsUpperTriangular(array $U)
  2383. {
  2384. // Given
  2385. $U = MatrixFactory::create($U);
  2386. // When
  2387. $U+U = $U->add($U);
  2388. // Then
  2389. $this->assertTrue($U->isUpperTriangular());
  2390. $this->assertTrue($U+U->isUpperTriangular());
  2391. }
  2392. /**
  2393. * @test Axiom: L⁻¹ is lower triangular (If L is invertible)
  2394. * The inverse of an invertible lower triangular matrix is lower triangular
  2395. * @dataProvider dataProviderForLowerTriangularMatrix
  2396. * @param array $L
  2397. * @throws \Exception
  2398. */
  2399. public function testInverseOfInvertibleLowerTriangularMatrixIsLowerTriangular(array $L)
  2400. {
  2401. // Given
  2402. $L = MatrixFactory::create($L);
  2403. $this->assertTrue($L->isLowerTriangular());
  2404. if ($L->isInvertible()) {
  2405. // When
  2406. $L⁻¹ = $L->inverse();
  2407. // Then
  2408. $this->assertTrue($L⁻¹->isLowerTriangular());
  2409. }
  2410. }
  2411. /**
  2412. * @test Axiom: U⁻¹ is upper triangular (If U is invertible)
  2413. * The inverse of an invertible upper triangular matrix is upper triangular
  2414. * @dataProvider dataProviderForUpperTriangularMatrix
  2415. * @param array $U
  2416. * @throws \Exception
  2417. */
  2418. public function testInverseOfInvertibleUpperTriangularMatrixIsUpperTriangular(array $U)
  2419. {
  2420. // Given
  2421. $U = MatrixFactory::create($U);
  2422. $this->assertTrue($U->isUpperTriangular());
  2423. if ($U->isInvertible()) {
  2424. // When
  2425. $U⁻¹ = $U->inverse();
  2426. // Then
  2427. $this->assertTrue($U⁻¹->isUpperTriangular());
  2428. }
  2429. }
  2430. /**
  2431. * @test Axiom: kL is lower triangular
  2432. * Product of a lower triangular matrix by a constant is lower triangular
  2433. * @dataProvider dataProviderForLowerTriangularMatrix
  2434. * @param array $L
  2435. * @throws \Exception
  2436. */
  2437. public function testProductOfLowerTriangularMatrixByConstantIsLowerTriangular(array $L)
  2438. {
  2439. // Given
  2440. $L = MatrixFactory::create($L);
  2441. $this->assertTrue($L->isLowerTriangular());
  2442. foreach (\range(1, 10) as $k) {
  2443. // When
  2444. $kL = $L->scalarMultiply($k);
  2445. // Then
  2446. $this->assertTrue($kL->isLowerTriangular());
  2447. }
  2448. }
  2449. /**
  2450. * @test Axiom: kU is upper triangular
  2451. * Product of a upper triangular matrix by a constant is upper triangular
  2452. * @dataProvider dataProviderForUpperTriangularMatrix
  2453. * @param array $U
  2454. * @throws \Exception
  2455. */
  2456. public function testProductOfUpperTriangularMatrixByConstantIsUpperTriangular(array $U)
  2457. {
  2458. // Given
  2459. $U = MatrixFactory::create($U);
  2460. $this->assertTrue($U->isUpperTriangular());
  2461. foreach (\range(1, 10) as $k) {
  2462. // When
  2463. $kU = $U->scalarMultiply($k);
  2464. // Then
  2465. $this->assertTrue($kU->isUpperTriangular());
  2466. }
  2467. }
  2468. /**
  2469. * @test Axiom: L is invertible iff diagonal is all non zero
  2470. * Lower triangular matrix is invertible if and only if its diagonal entries are all non zero
  2471. * @dataProvider dataProviderForLowerTriangularMatrix
  2472. * @param array $L
  2473. * @throws \Exception
  2474. */
  2475. public function testLowerTriangularMatrixIsInvertibleIfAndOnlyIfDigaonalEntriesAreAllNonZero(array $L)
  2476. {
  2477. // Given
  2478. $L = MatrixFactory::create($L);
  2479. $this->assertTrue($L->isLowerTriangular());
  2480. $zeros = \array_filter(
  2481. $L->getDiagonalElements(),
  2482. function ($x) {
  2483. return $x == 0;
  2484. }
  2485. );
  2486. // Then
  2487. if (count($zeros) == 0) {
  2488. $this->assertTrue($L->isInvertible());
  2489. } else {
  2490. $this->assertFalse($L->isInvertible());
  2491. }
  2492. }
  2493. /**
  2494. * @test Axiom: U is invertible iff diagonal is all non zero
  2495. * Upper triangular matrix is invertible if and only if its diagonal entries are all non zero
  2496. * @dataProvider dataProviderForUpperTriangularMatrix
  2497. * @param array $U
  2498. * @throws \Exception
  2499. */
  2500. public function testUpperTriangularMatrixIsInvertibleIfAndOnlyIfDigaonalEntriesAreAllNonZero(array $U)
  2501. {
  2502. // Given
  2503. $U = MatrixFactory::create($U);
  2504. $this->assertTrue($U->isUpperTriangular());
  2505. $zeros = \array_filter(
  2506. $U->getDiagonalElements(),
  2507. function ($x) {
  2508. return $x == 0;
  2509. }
  2510. );
  2511. // Then
  2512. if (count($zeros) == 0) {
  2513. $this->assertTrue($U->isInvertible());
  2514. } else {
  2515. $this->assertFalse($U->isInvertible());
  2516. }
  2517. }
  2518. /**
  2519. * @test Axiom: Dᵀ is diagonal
  2520. * Transpose of a diagonal matrix is diagonal
  2521. * @dataProvider dataProviderForDiagonalMatrix
  2522. * @param array $D
  2523. * @throws \Exception
  2524. */
  2525. public function testTransposeOfDiagonalMatrixIsDiagonal(array $D)
  2526. {
  2527. // Given
  2528. $D = MatrixFactory::create($D);
  2529. // When
  2530. $Dᵀ = $D->Transpose();
  2531. // Then
  2532. $this->assertTrue($D->isDiagonal());
  2533. $this->assertTrue($Dᵀ->isDiagonal());
  2534. }
  2535. /**
  2536. * @test Axiom: DD is diagonal
  2537. * Product of two diagonal matrices is diagonal
  2538. * @dataProvider dataProviderForDiagonalMatrix
  2539. * @param array $D
  2540. * @throws \Exception
  2541. */
  2542. public function testProductOfTwoDiagonalMatricesIsDiagonal(array $D)
  2543. {
  2544. // Given
  2545. $D = MatrixFactory::create($D);
  2546. // When
  2547. $DD = $D->multiply($D);
  2548. // Then
  2549. $this->assertTrue($D->isDiagonal());
  2550. $this->assertTrue($DD->isDiagonal());
  2551. }
  2552. /**
  2553. * @test Axiom: D + D is diagonal
  2554. * Sum of two diagonal matrices is diagonal
  2555. * @dataProvider dataProviderForDiagonalMatrix
  2556. * @param array $D
  2557. * @throws \Exception
  2558. */
  2559. public function testSumOfTwoDiagonalMatricesIsDiagonal(array $D)
  2560. {
  2561. // Given
  2562. $D = MatrixFactory::create($D);
  2563. // When
  2564. $D+D = $D->add($D);
  2565. // Then
  2566. $this->assertTrue($D->isDiagonal());
  2567. $this->assertTrue($D+D->isDiagonal());
  2568. }
  2569. /**
  2570. * @test Axiom: D⁻¹ is diagonal (If D is invertible)
  2571. * The inverse of an invertible diagonal matrix is diagonal
  2572. * @dataProvider dataProviderForDiagonalMatrix
  2573. * @param array $D
  2574. * @throws \Exception
  2575. */
  2576. public function testInverseOfInvertibleDiagonalMatrixIsDiagonal(array $D)
  2577. {
  2578. // Given
  2579. $D = MatrixFactory::create($D);
  2580. $this->assertTrue($D->isDiagonal());
  2581. if ($D->isInvertible()) {
  2582. // When
  2583. $D⁻¹ = $D->inverse();
  2584. // Then
  2585. $this->assertTrue($D⁻¹->isDiagonal());
  2586. }
  2587. }
  2588. /**
  2589. * @test Axiom: kD is Diagonal
  2590. * Product of a diagonal matrix by a constant is diagonal
  2591. * @dataProvider dataProviderForDiagonalMatrix
  2592. * @param array $D
  2593. * @throws \Exception
  2594. */
  2595. public function testProductOfDiagonalMatrixByConstantIsDiagonal(array $D)
  2596. {
  2597. // Given
  2598. $D = MatrixFactory::create($D);
  2599. $this->assertTrue($D->isDiagonal());
  2600. foreach (\range(1, 10) as $k) {
  2601. // When
  2602. $kD = $D->scalarMultiply($k);
  2603. // Then
  2604. $this->assertTrue($kD->isDiagonal());
  2605. }
  2606. }
  2607. /**
  2608. * @test Axiom: D is invertible iff diagonal is all non zero
  2609. * Diagonal matrix is invertible if and only if its diagonal entries are all non zero
  2610. * @dataProvider dataProviderForDiagonalMatrix
  2611. * @param array $D
  2612. * @throws \Exception
  2613. */
  2614. public function testDiagonalMatrixIsInvertibleIfAndOnlyIfDigaonalEntriesAreAllNonZero(array $D)
  2615. {
  2616. // Given
  2617. $D = MatrixFactory::create($D);
  2618. $this->assertTrue($D->isDiagonal());
  2619. $zeros = \array_filter(
  2620. $D->getDiagonalElements(),
  2621. function ($x) {
  2622. return $x == 0;
  2623. }
  2624. );
  2625. // Then
  2626. if (count($zeros) == 0) {
  2627. $this->assertTrue($D->isInvertible());
  2628. } else {
  2629. $this->assertFalse($D->isInvertible());
  2630. }
  2631. }
  2632. /**
  2633. * @test Axiom: Reduced row echelon form is upper triangular
  2634. * @dataProvider dataProviderForSquareMatrix
  2635. * @param array $A
  2636. * @throws \Exception
  2637. */
  2638. public function testReducedRowEchelonFormIsUpperTriangular(array $A)
  2639. {
  2640. // Given
  2641. $A = MatrixFactory::create($A);
  2642. // When
  2643. $rref = $A->rref();
  2644. // Then
  2645. $this->assertTrue($rref->isUpperTriangular());
  2646. }
  2647. /**
  2648. * @test Axiom: Jᵀ = J
  2649. * Transpose of an exchange matrix is itself
  2650. * @throws \Exception
  2651. */
  2652. public function testTransposeOfExchangeMatrix()
  2653. {
  2654. foreach (\range(1, 20) as $n) {
  2655. // Given
  2656. $J = MatrixFactory::exchange($n);
  2657. // When
  2658. $Jᵀ = $J->transpose();
  2659. // Then
  2660. $this->assertEquals($J->getMatrix(), $Jᵀ->getMatrix());
  2661. }
  2662. }
  2663. /**
  2664. * @test Axiom: J⁻¹ = J
  2665. * Inverse of an exchange matrix is itself
  2666. * @throws \Exception
  2667. */
  2668. public function testInverseOfExchangeMatrix()
  2669. {
  2670. foreach (\range(1, 20) as $n) {
  2671. // Given
  2672. $J = MatrixFactory::exchange($n);
  2673. // When
  2674. $J⁻¹ = $J->inverse();
  2675. // Then
  2676. $this->assertEquals($J->getMatrix(), $J⁻¹->getMatrix());
  2677. }
  2678. }
  2679. /**
  2680. * @test Axiom: tr(J) is 1 if n is odd, and 0 if n is even
  2681. * Trace of J is 1 if n is odd, and 0 is n is even.
  2682. * @throws \Exception
  2683. */
  2684. public function testTraceOfExchangeMatrix()
  2685. {
  2686. foreach (\range(1, 20) as $n) {
  2687. // Given
  2688. $J = MatrixFactory::exchange($n);
  2689. // When
  2690. $tr⟮J⟯ = $J->trace();
  2691. // Then
  2692. if (Integer::isOdd($n)) {
  2693. $this->assertEquals(1, $tr⟮J⟯);
  2694. } else {
  2695. $this->assertEquals(0, $tr⟮J⟯);
  2696. }
  2697. }
  2698. }
  2699. /**
  2700. * @test Axiom: Signature matrix is involutory
  2701. * @dataProvider dataProviderForSignatureMatrix
  2702. * @param array $A
  2703. * @throws \Exception
  2704. */
  2705. public function testSignatureMatrixIsInvolutory(array $A)
  2706. {
  2707. // Given
  2708. $A = MatrixFactory::create($A);
  2709. // Then
  2710. $this->assertTrue($A->isSignature());
  2711. $this->assertTrue($A->isInvolutory());
  2712. }
  2713. /**
  2714. * @test Axiom: Hilbert matrix is symmetric
  2715. * @throws \Exception
  2716. */
  2717. public function testHilbertMatrixIsSymmetric()
  2718. {
  2719. foreach (\range(1, 10) as $n) {
  2720. // Given
  2721. $H = MatrixFactory::hilbert($n);
  2722. // Then
  2723. $this->assertTrue($H->isSymmetric());
  2724. }
  2725. }
  2726. /**
  2727. * @test Axiom: Hilbert matrix is positive definite
  2728. * @throws \Exception
  2729. */
  2730. public function testHilbertMatrixIsPositiveDefinite()
  2731. {
  2732. foreach (\range(1, 10) as $n) {
  2733. // Given
  2734. $H = MatrixFactory::hilbert($n);
  2735. // Then
  2736. $this->assertTrue($H->isPositiveDefinite());
  2737. }
  2738. }
  2739. /**
  2740. * @test Axiom: A = LLᵀ (Cholesky decomposition)
  2741. * @dataProvider dataProviderForPositiveDefiniteMatrix
  2742. * @param array $A
  2743. * @throws \Exception
  2744. */
  2745. public function testCholeskyDecompositionLTimesLTransposeIsA(array $A)
  2746. {
  2747. // Given
  2748. $A = MatrixFactory::create($A);
  2749. $cholesky = $A->choleskyDecomposition();
  2750. $L = $cholesky->L;
  2751. $Lᵀ = $cholesky->LT;
  2752. // When
  2753. $LLᵀ = $L->multiply($Lᵀ);
  2754. // Then
  2755. $this->assertEqualsWithDelta($A->getMatrix(), $LLᵀ->getMatrix(), 0.00001);
  2756. }
  2757. /**
  2758. * @test Axiom: L is lower triangular (Cholesky decomposition)
  2759. * @dataProvider dataProviderForPositiveDefiniteMatrix
  2760. * @param array $A
  2761. * @throws \Exception
  2762. */
  2763. public function testCholeskyDecompositionLIsLowerTriangular(array $A)
  2764. {
  2765. // Given
  2766. $A = MatrixFactory::create($A);
  2767. // When
  2768. $cholesky = $A->choleskyDecomposition();
  2769. // Then
  2770. $this->assertTrue($cholesky->L->isLowerTriangular());
  2771. }
  2772. /**
  2773. * @test Axiom: Lᵀ is upper triangular (Cholesky decomposition)
  2774. * @dataProvider dataProviderForPositiveDefiniteMatrix
  2775. * @param array $A
  2776. * @throws \Exception
  2777. */
  2778. public function testCholeskyDecompositionLTransposeIsUpperTriangular(array $A)
  2779. {
  2780. // Given
  2781. $A = MatrixFactory::create($A);
  2782. // When
  2783. $cholesky = $A->choleskyDecomposition();
  2784. // Then
  2785. $this->assertTrue($cholesky->LT->isUpperTriangular());
  2786. }
  2787. /**
  2788. * @test Axiom: adj⟮A⟯ = Cᵀ
  2789. * Adjugate matrix equals the transpose of the cofactor matrix
  2790. * @dataProvider dataProviderForSquareMatrixGreaterThanOne
  2791. * @param array $A
  2792. * @throws \Exception
  2793. */
  2794. public function testAdjugateIsTransoseOfCofactorMatrix(array $A)
  2795. {
  2796. // Given
  2797. $A = MatrixFactory::create($A);
  2798. // When
  2799. $adj⟮A⟯ = $A->adjugate();
  2800. $Cᵀ = $A->cofactorMatrix()->transpose();
  2801. // Then
  2802. $this->assertEqualsWithDelta($adj⟮A⟯, $Cᵀ, 0.00001);
  2803. }
  2804. /**
  2805. * @test Axiom: A adj⟮A⟯ = det⟮A⟯ I
  2806. * The product of A with its adjugate yields a diagonal matrix whose diagonal entries are det(A)
  2807. * @dataProvider dataProviderForSquareMatrixGreaterThanOne
  2808. * @param array $A
  2809. * @throws \Exception
  2810. */
  2811. public function testAdjugateTimesAIsIdentityMatrixTimesDeterminantOfA(array $A)
  2812. {
  2813. // Given
  2814. $A = MatrixFactory::create($A);
  2815. // When
  2816. $adj⟮A⟯ = $A->adjugate();
  2817. $Aadj⟮A⟯ = $A->multiply($adj⟮A⟯);
  2818. // And
  2819. $I = MatrixFactory::identity($A->getN());
  2820. $det⟮A⟯ = $A->det();
  2821. $det⟮A⟯I = $I->scalarMultiply($det⟮A⟯);
  2822. // Then
  2823. $this->assertEqualsWithDelta($Aadj⟮A⟯, $det⟮A⟯I, 0.00001);
  2824. }
  2825. /**
  2826. * @test Axiom: adj⟮A⟯ = det⟮A⟯A⁻¹
  2827. * The product of A with its adjugate yields a diagonal matrix whose diagonal entries are det(A)
  2828. * @dataProvider dataProviderForNonsingularMatrix
  2829. * @param array $A
  2830. * @throws \Exception
  2831. */
  2832. public function testAdjugateEqualsInverseOfATimesDeterminant(array $A)
  2833. {
  2834. // Given
  2835. $A = MatrixFactory::create($A);
  2836. // When
  2837. $A⁻¹ = $A->inverse();
  2838. $adj⟮A⟯ = $A->adjugate();
  2839. $det⟮A⟯ = $A->det();
  2840. $det⟮A⟯A⁻¹ = $A⁻¹->scalarMultiply($det⟮A⟯);
  2841. // Then
  2842. $this->assertEqualsWithDelta($adj⟮A⟯, $det⟮A⟯A⁻¹, 0.00001);
  2843. }
  2844. /**
  2845. * @test Axiom: A⁻¹ = (1/det⟮A⟯) adj⟮A⟯
  2846. * The inverse of a matrix is equals to one over the determinant multiplied by the adjugate
  2847. * @dataProvider dataProviderForNonsingularMatrix
  2848. * @param array $A
  2849. * @throws \Exception
  2850. */
  2851. public function testInverseEqualsOneOverDetTimesAdjugate(array $A)
  2852. {
  2853. // Given
  2854. $A = MatrixFactory::create($A);
  2855. // When
  2856. $A⁻¹ = $A->inverse();
  2857. $adj⟮A⟯ = $A->adjugate();
  2858. $det⟮A⟯ = $A->det();
  2859. $⟮1/det⟮A⟯⟯adj⟮A⟯ = $adj⟮A⟯->scalarMultiply(1 / $det⟮A⟯);
  2860. // Then
  2861. $this->assertEqualsWithDelta($A⁻¹, $⟮1/det⟮A⟯⟯adj⟮A⟯, 0.00001);
  2862. }
  2863. /**
  2864. * @test Axiom: adj⟮I⟯ = I
  2865. * The adjugate of identity matrix is identity matrix
  2866. * @dataProvider dataProviderForIdentityMatrix
  2867. * @param array $I
  2868. * @throws \Exception
  2869. */
  2870. public function testAdjugateOfIdenetityMatrixIsIdentity(array $I)
  2871. {
  2872. // Given
  2873. $I = MatrixFactory::create($I);
  2874. // When
  2875. $adj⟮I⟯ = $I->adjugate();
  2876. // Then
  2877. $this->assertEqualsWithDelta($adj⟮I⟯, $I, 0.00001);
  2878. }
  2879. /**
  2880. * @test Axiom: adj⟮AB⟯ = adj⟮B⟯adj⟮A⟯
  2881. * The adjugate of AB equals the adjugate of B times the adjugate of A
  2882. * @dataProvider dataProviderForTwoNonsingularMatrices
  2883. * @param array $A
  2884. * @param array $B
  2885. * @throws \Exception
  2886. */
  2887. public function testAdjugateABEqualsAdjugateBTimesAdjugateA(array $A, array $B)
  2888. {
  2889. // Given
  2890. $A = MatrixFactory::create($A);
  2891. $B = MatrixFactory::create($B);
  2892. // When
  2893. $AB = $A->multiply($B);
  2894. $adj⟮A⟯ = $A->adjugate();
  2895. $adj⟮B⟯ = $B->adjugate();
  2896. $adj⟮AB⟯ = $AB->adjugate();
  2897. $adj⟮B⟯adj⟮A⟯ = $adj⟮B⟯->multiply($adj⟮A⟯);
  2898. // Then
  2899. $this->assertEqualsWithDelta($adj⟮AB⟯, $adj⟮B⟯adj⟮A⟯, 0.00001);
  2900. }
  2901. /**
  2902. * @test Axiom: adj⟮cA⟯ = cⁿ⁻¹ adj⟮A⟯
  2903. * The adjugate of a matrix times a scalar equals the adjugate of the matrix then times a scalar raised to n - 1
  2904. * @dataProvider dataProviderForNonsingularMatrix
  2905. * @param array $A
  2906. * @throws \Exception
  2907. */
  2908. public function testAdjugateAtimesCEqualsAdjugateATimesCRaisedToNMinusOne(array $A)
  2909. {
  2910. // Given
  2911. $c = 4;
  2912. $A = MatrixFactory::create($A);
  2913. // When
  2914. $cA = $A->scalarMultiply($c);
  2915. $adj⟮A⟯ = $A->adjugate();
  2916. $adj⟮cA⟯ = $cA->adjugate();
  2917. $cⁿ⁻¹ = \pow($c, $A->getN() - 1);
  2918. $cⁿ⁻¹adj⟮A⟯ = $adj⟮A⟯->scalarMultiply($cⁿ⁻¹);
  2919. // Then
  2920. $this->assertEqualsWithDelta($adj⟮cA⟯, $cⁿ⁻¹adj⟮A⟯, 0.00001);
  2921. }
  2922. /**
  2923. * @test Axiom: adj⟮B⟯adj⟮A⟯ = det⟮B⟯B⁻¹ det⟮A⟯A⁻¹ = det⟮AB⟯⟮AB⟯⁻¹
  2924. * The adjugate of B times adjugate A equals the determinant of B times inverse of B times determinant of A times inverse of A
  2925. * which equals the determinant of AB times the inverse of AB
  2926. * @dataProvider dataProviderForTwoNonsingularMatrices
  2927. * @param array $A
  2928. * @param array $B
  2929. * @throws \Exception
  2930. */
  2931. public function testAdjugateBTimesAdjugateAEqualsDetBTimesInverseBTimesDetATimesInverseAEqualsDetABTimesInverseAB(array $A, array $B)
  2932. {
  2933. // Given
  2934. $A = MatrixFactory::create($A);
  2935. $B = MatrixFactory::create($B);
  2936. $A⁻¹ = $A->inverse();
  2937. $B⁻¹ = $B->inverse();
  2938. $AB = $A->multiply($B);
  2939. $⟮AB⟯⁻¹ = $AB->inverse();
  2940. $adj⟮A⟯ = $A->adjugate();
  2941. $adj⟮B⟯ = $B->adjugate();
  2942. $det⟮A⟯ = $A->det();
  2943. $det⟮B⟯ = $B->det();
  2944. $det⟮AB⟯ = $AB->det();
  2945. // When
  2946. $det⟮A⟯A⁻¹ = $A⁻¹->scalarMultiply($det⟮A⟯);
  2947. $det⟮B⟯B⁻¹ = $B⁻¹->scalarMultiply($det⟮B⟯);
  2948. // And
  2949. $adj⟮B⟯adj⟮A⟯ = $adj⟮B⟯->multiply($adj⟮A⟯);
  2950. $det⟮B⟯B⁻¹det⟮A⟯A⁻¹ = $det⟮B⟯B⁻¹->multiply($det⟮A⟯A⁻¹);
  2951. $det⟮AB⟯⟮AB⟯⁻¹ = $⟮AB⟯⁻¹->scalarMultiply($det⟮AB⟯);
  2952. // Then
  2953. $this->assertEqualsWithDelta($adj⟮B⟯adj⟮A⟯, $det⟮B⟯B⁻¹det⟮A⟯A⁻¹, 0.001);
  2954. $this->assertEqualsWithDelta($det⟮B⟯B⁻¹det⟮A⟯A⁻¹, $det⟮AB⟯⟮AB⟯⁻¹, 0.001);
  2955. $this->assertEqualsWithDelta($adj⟮B⟯adj⟮A⟯, $det⟮AB⟯⟮AB⟯⁻¹, 0.001);
  2956. }
  2957. /**
  2958. * @test Axiom: adj⟮Aᵀ⟯ = adj⟮A⟯ᵀ
  2959. * The adjugate of a matrix transpase equals the transpose of a matrix adjugate
  2960. * @dataProvider dataProviderForNonsingularMatrix
  2961. * @param array $A
  2962. * @throws \Exception
  2963. */
  2964. public function testAdjugateOfTransposeEqualsTransposeOfAdjugate(array $A)
  2965. {
  2966. // Given
  2967. $A = MatrixFactory::create($A);
  2968. // When
  2969. $Aᵀ = $A->transpose();
  2970. $adj⟮A⟯ = $A->adjugate();
  2971. $adj⟮Aᵀ⟯ = $Aᵀ->adjugate();
  2972. $adj⟮A⟯ᵀ = $adj⟮A⟯->transpose();
  2973. // Then
  2974. $this->assertEqualsWithDelta($adj⟮Aᵀ⟯, $adj⟮A⟯ᵀ, 0.00001);
  2975. }
  2976. /**
  2977. * @test Axiom: Aadj⟮A⟯ = adj⟮A⟯A = det⟮A⟯I
  2978. * A matrix times its adjugate equals the adjugate times the matrix which equals the identity matrix times the determinant
  2979. * @dataProvider dataProviderForNonsingularMatrix
  2980. * @param array $A
  2981. * @throws \Exception
  2982. */
  2983. public function testMatrixTimesItsAdjugateEqualsAdjugateTimesMatrixEqualsDetTimesIdentity(array $A)
  2984. {
  2985. // Given
  2986. $A = MatrixFactory::create($A);
  2987. // When
  2988. $adj⟮A⟯ = $A->adjugate();
  2989. $Aadj⟮A⟯ = $A->multiply($adj⟮A⟯);
  2990. $adj⟮A⟯A = $adj⟮A⟯->multiply($A);
  2991. $det⟮A⟯ = $A->det();
  2992. $I = MatrixFactory::identity($A->getN());
  2993. $det⟮A⟯I = $I->scalarMultiply($det⟮A⟯);
  2994. // Then
  2995. $this->assertEqualsWithDelta($Aadj⟮A⟯, $adj⟮A⟯A, 0.0001);
  2996. $this->assertEqualsWithDelta($Aadj⟮A⟯, $det⟮A⟯I, 0.0001);
  2997. $this->assertEqualsWithDelta($adj⟮A⟯A, $det⟮A⟯I, 0.0001);
  2998. }
  2999. /**
  3000. * @test Axiom: rank(A) ≤ min(m, n)
  3001. * The rank of a matrix is less than or equal to the minimum dimension of the matrix
  3002. * @dataProvider dataProviderForSingleMatrix
  3003. * @param array $A
  3004. * @throws \Exception
  3005. */
  3006. public function testRankLessThanMinDimension(array $A)
  3007. {
  3008. // Given
  3009. $A = MatrixFactory::create($A);
  3010. // Then
  3011. $this->assertLessThanOrEqual(\min($A->getM(), $A->getN()), $A->rank());
  3012. }
  3013. /**
  3014. * @test Axiom: Zero matrix has rank of 0
  3015. * @throws \Exception
  3016. */
  3017. public function testZeroMatrixHasRankOfZero()
  3018. {
  3019. foreach (\range(1, 10) as $m) {
  3020. foreach (\range(1, 10) as $n) {
  3021. // Given
  3022. $A = MatrixFactory::zero($m, $n);
  3023. // Then
  3024. $this->assertEquals(0, $A->rank());
  3025. }
  3026. }
  3027. }
  3028. /**
  3029. * @test Axiom: If A is square matrix, then it is invertible only if rank = n (full rank)
  3030. * @dataProvider dataProviderForSquareMatrix
  3031. * @param array $A
  3032. * @throws \Exception
  3033. */
  3034. public function testSquareMatrixInvertibleIfFullRank(array $A)
  3035. {
  3036. // Given
  3037. $A = MatrixFactory::create($A);
  3038. // When
  3039. $rank = $A->rank();
  3040. // Then
  3041. if ($rank === $A->getM()) {
  3042. $this->assertTrue($A->isInvertible());
  3043. } else {
  3044. $this->assertFalse($A->isInvertible());
  3045. }
  3046. }
  3047. /**
  3048. * @test Axiom: rank(AᵀA) = rank(AAᵀ) = rank(A) = rank(Aᵀ)
  3049. * @dataProvider dataProviderForSingleMatrix
  3050. * @param array $A
  3051. * @throws \Exception
  3052. */
  3053. public function testRankTransposeEqualities(array $A)
  3054. {
  3055. // Given
  3056. $A = MatrixFactory::create($A);
  3057. $Aᵀ = $A->transpose();
  3058. $AᵀA = $Aᵀ->multiply($A);
  3059. $AAᵀ = $A->multiply($Aᵀ);
  3060. // When
  3061. $rank⟮A⟯ = $A->rank();
  3062. $rank⟮Aᵀ⟯ = $Aᵀ->rank();
  3063. $rank⟮AᵀA⟯ = $AᵀA->rank();
  3064. $rank⟮AAᵀ⟯ = $AAᵀ->rank();
  3065. // Then
  3066. $this->assertEquals($rank⟮A⟯, $rank⟮Aᵀ⟯);
  3067. $this->assertEquals($rank⟮A⟯, $rank⟮AᵀA⟯);
  3068. $this->assertEquals($rank⟮A⟯, $rank⟮AAᵀ⟯);
  3069. $this->assertEquals($rank⟮Aᵀ⟯, $rank⟮AᵀA⟯);
  3070. $this->assertEquals($rank⟮Aᵀ⟯, $rank⟮AAᵀ⟯);
  3071. $this->assertEquals($rank⟮AᵀA⟯, $rank⟮AAᵀ⟯);
  3072. }
  3073. /**
  3074. * @test Axiom: Lower bidiagonal matrix is upper Hessenberg
  3075. * @dataProvider dataProviderForLowerBidiagonalMatrix
  3076. * @param array $A
  3077. * @throws \Exception
  3078. */
  3079. public function testLowerBidiagonalMatrixIsUpperHessenberg(array $A)
  3080. {
  3081. // Given
  3082. $A = MatrixFactory::create($A);
  3083. // Then
  3084. $this->assertTrue($A->isLowerBidiagonal());
  3085. $this->assertTrue($A->isUpperHessenberg());
  3086. }
  3087. /**
  3088. * @test Axiom: Upper bidiagonal matrix is lower Hessenberg
  3089. * @dataProvider dataProviderForUpperBidiagonalMatrix
  3090. * @param array $A
  3091. * @throws \Exception
  3092. */
  3093. public function testUpperBidiagonalMatrixIsLowerHessenberg(array $A)
  3094. {
  3095. // Given
  3096. $A = MatrixFactory::create($A);
  3097. // Then
  3098. $this->assertTrue($A->isUpperBidiagonal());
  3099. $this->assertTrue($A->isLowerHessenberg());
  3100. }
  3101. /**
  3102. * @test Axiom: A matrix that is both upper Hessenberg and lower Hessenberg is a tridiagonal matrix
  3103. * @dataProvider dataProviderForTridiagonalMatrix
  3104. * @param array $A
  3105. * @throws \Exception
  3106. */
  3107. public function testTridiagonalMatrixIsUpperAndLowerHessenberg(array $A)
  3108. {
  3109. // Given
  3110. $A = MatrixFactory::create($A);
  3111. // Then
  3112. $this->assertTrue($A->isTridiagonal());
  3113. $this->assertTrue($A->isUpperHessenberg());
  3114. $this->assertTrue($A->isLowerHessenberg());
  3115. }
  3116. /**
  3117. * @test Axiom: AAᵀ = I for an orthogonal matrix A
  3118. * @dataProvider dataProviderForOrthogonalMatrix
  3119. * @param array $A
  3120. * @throws \Exception
  3121. */
  3122. public function testOrthogonalMatrixTimesTransposeIsIdentityMatrix(array $A)
  3123. {
  3124. // Given
  3125. $A = MatrixFactory::create($A);
  3126. $Aᵀ = $A->transpose();
  3127. $I = MatrixFactory::identity($A->getM());
  3128. // When
  3129. $AAᵀ = $A->multiply($Aᵀ);
  3130. // Then
  3131. $this->assertEqualsWithDelta($I->getMatrix(), $AAᵀ->getMatrix(), 0.00001);
  3132. }
  3133. /**
  3134. * @test Axiom: AᵀA = I for an orthogonal matrix A
  3135. * @dataProvider dataProviderForOrthogonalMatrix
  3136. * @param array $A
  3137. * @throws \Exception
  3138. */
  3139. public function testOrthogonalTransposeOfOrthogonalMatrixTimesMatrixIsIdentityMatrix(array $A)
  3140. {
  3141. // Given
  3142. $A = MatrixFactory::create($A);
  3143. $Aᵀ = $A->transpose();
  3144. $I = MatrixFactory::identity($A->getM());
  3145. // When
  3146. $AᵀA = $Aᵀ->multiply($A);
  3147. // Then
  3148. $this->assertEqualsWithDelta($I->getMatrix(), $AᵀA->getMatrix(), 0.00001);
  3149. }
  3150. /**
  3151. * @test Axiom: A⁻¹ = Aᵀ for an orthogonal matrix A
  3152. * @dataProvider dataProviderForOrthogonalMatrix
  3153. * @param array $A
  3154. * @throws \Exception
  3155. */
  3156. public function testOrthogonalMatrixInverseEqualsTransposeOfOrthogonalMatrix(array $A)
  3157. {
  3158. // Given
  3159. $A = MatrixFactory::create($A);
  3160. // When
  3161. $Aᵀ = $A->transpose();
  3162. $A⁻¹ = $A->inverse();
  3163. // Then
  3164. $this->assertEqualsWithDelta($A⁻¹->getMatrix(), $Aᵀ->getMatrix(), 0.00001);
  3165. }
  3166. /**
  3167. * @test Axiom: det(A) = ±1 for an orthogonal matrix A
  3168. * @dataProvider dataProviderForOrthogonalMatrix
  3169. * @param array $A
  3170. * @throws \Exception
  3171. */
  3172. public function testOrthogonalMatrixDeterminateIsOne(array $A)
  3173. {
  3174. // Given
  3175. $A = MatrixFactory::create($A);
  3176. // When
  3177. $det⟮A⟯ = $A->det();
  3178. // Then
  3179. $this->assertEqualsWithDelta(1, \abs($det⟮A⟯), 0.000001);
  3180. }
  3181. /**
  3182. * @test Axiom: Householder transformation creates a matrix that is involutory
  3183. * @dataProvider dataProviderForSquareMatrix
  3184. * @param array $A
  3185. * @throws \Exception
  3186. */
  3187. public function testHouseholderTransformMatrixInvolutoryProperty(array $A)
  3188. {
  3189. // Given
  3190. $A = MatrixFactory::create($A);
  3191. // When
  3192. $H = $A->householder();
  3193. // Then
  3194. $this->assertTrue($H->isInvolutory());
  3195. }
  3196. /**
  3197. * @test Axiom: Householder transformation creates a matrix with a determinant that is -1
  3198. * @dataProvider dataProviderForSquareMatrix
  3199. * @param array $A
  3200. * @throws \Exception
  3201. */
  3202. public function testHouseholderTransformMatrixDeterminant(array $A)
  3203. {
  3204. // Given
  3205. $A = MatrixFactory::create($A);
  3206. // When
  3207. $H = $A->householder();
  3208. // Then
  3209. $this->assertEqualsWithDelta(-1, $H->det(), 0.000001);
  3210. }
  3211. /**
  3212. * @test Axiom: Householder transformation creates a matrix that has eigenvalues 1 and -1
  3213. * @dataProvider dataProviderForSquareMatrixGreaterThanOne
  3214. * @param array $A
  3215. * @throws \Exception
  3216. */
  3217. public function testHouseholderTransformMatrixEigenvalues(array $A)
  3218. {
  3219. // Given
  3220. $A = MatrixFactory::create($A);
  3221. // When
  3222. $H = $A->householder();
  3223. // Then
  3224. $eigenvalues = \array_filter(
  3225. $H->eigenvalues(),
  3226. function ($x) {
  3227. return !\is_nan($x);
  3228. }
  3229. );
  3230. $this->assertEqualsWithDelta(1, max($eigenvalues), 0.00001);
  3231. $this->assertEqualsWithDelta(-1, \min($eigenvalues), 0.00001);
  3232. }
  3233. /**
  3234. * @test Axiom: Nilpotent matrix - tr(Aᵏ) = 0 for all k > 0
  3235. * @dataProvider dataProviderForNilpotentMatrix
  3236. * @param array $A
  3237. * @throws \Exception
  3238. */
  3239. public function testNilpotentTraceIsZero(array $A)
  3240. {
  3241. // Given
  3242. $A = MatrixFactory::create($A);
  3243. foreach (\range(1, 5) as $_) {
  3244. // When
  3245. $A = $A->multiply($A);
  3246. $trace = $A->trace();
  3247. // Then
  3248. $this->assertEquals(0, $trace);
  3249. }
  3250. }
  3251. /**
  3252. * @test Axiom: Nilpotent matrix - det = 0
  3253. * @dataProvider dataProviderForNilpotentMatrix
  3254. * @param array $A
  3255. * @throws \Exception
  3256. */
  3257. public function testNilpotentDetIsZero(array $A)
  3258. {
  3259. // Given
  3260. $A = MatrixFactory::create($A);
  3261. // When
  3262. $det = $A->det();
  3263. // Then
  3264. $this->assertEquals(0, $det);
  3265. }
  3266. /**
  3267. * @test Axiom: Nilpotent matrix - Cannot be invertible
  3268. * @dataProvider dataProviderForNilpotentMatrix
  3269. * @param array $A
  3270. * @throws \Exception
  3271. */
  3272. public function testNilpotentCannotBeInvertible(array $A)
  3273. {
  3274. // Given
  3275. $A = MatrixFactory::create($A);
  3276. // When
  3277. $isInvertible = $A->isInvertible();
  3278. // Then
  3279. $this->assertFalse($isInvertible);
  3280. }
  3281. }