return function()local z=require(script.Parent.linalg)it("\115\104\111\117\108\100 \104\097\118\101 \105\109\109\117\116\097\098\108\101 \109\097\116\114\105\099\101\115",function()local U=z.matrix.new({{1;2};{3;4}});(expect(function()U[1]=1 end)).to.throw();(expect(function()U[1][1]=1 end)).to.throw()end)it("\115\104\111\117\108\100 \101\114\114\111\114 \111\110 \117\110\114\101\099\111\103\110\105\122\101\100 \107\101\121\115",function()local U=z.matrix.new({{1,2},{3;4}});(expect(function()local z=U["\073\110\118\097\108\105\100 \075\101\121"]end)).to.throw();(expect(function()local z=U[1]["\073\110\118\097\108\105\100 \075\101\121"]end)).to.throw()end)it("\115\104\111\117\108\100 \104\097\110\100\108\101 \101\113\117\097\108\105\116\121 \099\111\114\114\101\099\116\108\121",function()local U=z.matrix.new({{1,2};{3;4}})local Q=z.matrix.new({{1,3},{2,4}})local M=z.matrix.new({{1;2,3};{4,5;6}});(expect(U==U)).to.equal(true);(expect(U==Q)).to.equal(false);(expect(U==M)).to.equal(false);(expect(U[1]==U[1])).to.equal(true);(expect(U[1]==Q[1])).to.equal(false);(expect(U[1]==M[1])).to.equal(false)end)it("\115\104\111\117\108\100 \104\097\110\100\108\101 \109\097\116\114\105\120 \097\110\100 \115\099\097\108\097\114 \097\114\105\116\104\109\101\116\105\099 \099\111\114\114\101\099\116\108\121",function()local U=z.matrix.new({{1;2},{3,4}})local Q=z.matrix.new({{1,2,3},{4;5;6}});(expect(-U)).to.equal(z.matrix.new({{-1,-2};{-3;-4}}));(expect(function()local z=1+U end)).to.throw();(expect(function()local z=U+2 end)).to.throw();(expect(function()local z=1-U end)).to.throw();(expect(function()local z=U-2 end)).to.throw();(expect(2*U)).to.equal(z.matrix.new({{2,4},{6;8}}));(expect(2*U)).to.equal(U*2);(expect(U/2)).to.equal(z.matrix.new({{.5;1};{1.5,2}}));(expect(function()local z=2/U end)).to.throw();(expect(function()local z=U/U end)).to.throw();(expect(U%2)).to.equal(z.matrix.new({{1;0};{1,0}}));(expect(function()local z=1%U end)).to.throw();(expect(function()local z=U%U end)).to.throw();(expect(U^2)).to.equal(U*U);(expect(function()local z=Q^2 end)).to.throw();(expect(function()local z=U^2.5 end)).to.throw();(expect(function()local z=2^U end)).to.throw();(expect(function()local z=U^U end)).to.throw()end)it("\115\104\111\117\108\100 \104\097\110\100\108\101 \109\097\116\114\105\120 \097\110\100 \109\097\116\114\105\120 \097\114\105\116\104\109\101\116\105\099 \099\111\114\114\101\099\116\108\121",function()local U=z.matrix.new({{1;2};{3,4}});(expect(U+U)).to.equal(U*2);(expect(function()local Q=U+z.matrix.new({{1}})end)).to.throw();(expect(U-U)).to.equal(z.matrix.zeros(2,2));(expect(function()local Q=U-z.matrix.new({{1}})end)).to.throw();(expect(U*U)).to.equal(z.matrix.new({{7,10};{15,22}}));(expect(function()local Q=U*z.matrix.new({{1}})end)).to.throw()end)it("\115\104\111\117\108\100 \104\097\110\100\108\101 \097\114\105\116\104\109\101\116\105\099 \111\110 \114\111\119\115 \099\111\114\114\101\099\116\108\121",function()local U=z.matrix.new({{1;2},{3;4}})local Q=z.vector.new({1,2});(expect(-U[1])).to.equal((z.matrix.new({{-1;-2}}))[1]);(expect(-Q)).to.equal(z.vector.new({-1,-2}));(expect(-Q[1])).to.equal(-1);(expect(Q[1]+1)).to.equal(2);(expect(Q[1]+Q[1])).to.equal(2);(expect(U[1]+U[1])).to.equal((z.matrix.new({{2,4}}))[1]);(expect(function()local z=U[1]+1 end)).to.throw();(expect(function()local z=U[1]+Q[1]end)).to.throw();(expect(Q[1]-1)).to.equal(0);(expect(Q[1]-Q[1])).to.equal(0);(expect(U[1]-U[1])).to.equal((z.matrix.new({{0,0}}))[1]);(expect(function()local z=U[1]-1 end)).to.throw();(expect(function()local z=U[1]-Q[1]end)).to.throw();(expect(2*U[1])).to.equal((z.matrix.new({{2,4}}))[1]);(expect(Q[1]*U[1])).to.equal(U[1]);(expect(U[1]*Q[1])).to.equal(U[1]);(expect(2*Q[1])).to.equal(2);(expect(Q[1]*2)).to.equal(2);(expect(Q[1]*Q[1])).to.equal(1);(expect(function()local z=U[1]*U[1]end)).to.throw();(expect(U[2]/2)).to.equal((z.matrix.new({{1.5,2}}))[1]);(expect(U[1]/Q[1])).to.equal(U[1]);(expect(Q[2]/2)).to.equal(1);(expect(Q[1]/Q[1])).to.equal(1);(expect(function()local z=1/U[1]end)).to.throw();(expect(function()local z=Q[1]/U[1]end)).to.throw();(expect(function()local z=U[1]/U[1]end)).to.throw();(expect(U[2]%2)).to.equal((z.matrix.new({{1,0}}))[1]);(expect(U[1]%Q[2])).to.equal((z.matrix.new({{1,0}}))[1]);(expect(Q[1]%2)).to.equal(1);(expect(Q[1]%Q[2])).to.equal(1);(expect(function()local z=1%U[1]end)).to.throw();(expect(function()local z=Q[1]%U[1]end)).to.throw();(expect(function()local z=U[1]%U[1]end)).to.throw();(expect(2^Q[2])).to.equal(4);(expect(Q[2]^2)).to.equal(4);(expect(Q[2]^Q[2])).to.equal(4);(expect(function()local z=2^U[1]end)).to.throw();(expect(function()local z=U[1]^2 end)).to.throw();(expect(function()local z=Q[2]^U[1]end)).to.throw();(expect(function()local z=U[1]^Q[2]end)).to.throw();(expect(function()local z=U[1]^U[1]end)).to.throw()end)it("\115\104\111\117\108\100 \104\097\110\100\108\101 \098\097\115\105\099 \109\097\116\114\105\120 \111\112\101\114\097\116\105\111\110\115 \099\111\114\114\101\099\116\108\121",function()local U=z.matrix.new({{1;2};{3;4}})local Q=z.matrix.new({{1;2;3};{4,5;6}});(expect(U.T)).to.equal(z.matrix.new({{1;3},{2;4}}));(expect(U.Unit)).to.equal(z.matrix.new({{1,0},{0;1}}));(expect(function()local z=Q.Unit end)).to.throw()local M=U.expm();(expect(M[1][1])).to.be.near(51.968956198705);(expect(M[1][2])).to.be.near(74.736564567003);(expect(M[2][1])).to.be.near(112.1048468505);(expect(M[2][2])).to.be.near(164.07380304921);(expect(function()local U=(z.matrix.new({{1;2,3},{4;5,6}})).expm()end)).to.throw()end)it("\115\104\111\117\108\100 \099\114\101\097\116\101 \109\097\116\114\105\099\101\115 \099\111\114\114\101\099\116\108\121",function()(expect(z.matrix.fromColumns({{1;3};{2;4}}))).to.equal(z.matrix.new({{1;2},{3;4}}));(expect(z.matrix.fromColumns({z.matrix.new({{1},{3}});z.matrix.new({{2},{4}})}))).to.equal(z.matrix.new({{1,2};{3,4}}));(expect(z.matrix.identity(2))).to.equal(z.matrix.new({{1;0};{0;1}}));(expect(z.matrix.zeros(2,2))).to.equal(z.matrix.new({{0,0};{0,0}}));(expect(z.matrix.zerosLike(z.matrix.identity(2)))).to.equal(z.matrix.new({{0;0};{0;0}}));(expect(z.matrix.diagonal({1,2}))).to.equal(z.matrix.new({{1,0};{0,2}}))end)it("\115\104\111\117\108\100 \099\108\097\115\115\105\102\121 \109\097\116\114\105\099\101\115 \099\111\114\114\101\099\116\108\121",function()(expect((z.matrix.identity(2)).isDiagonal())).to.equal(true);(expect((z.matrix.new({{1;0,0};{0;1,0}})).isDiagonal())).to.equal(true);(expect((z.matrix.new({{1,2};{3,4}})).isDiagonal())).to.equal(false);(expect((z.matrix.identity(2)).isUpperTriangular())).to.equal(true);(expect((z.matrix.new({{1;2};{3,4}})).isUpperTriangular())).to.equal(false);(expect((z.matrix.new({{1,2,3};{4,5,6}})).isUpperTriangular())).to.equal(false);(expect((z.matrix.identity(2)).isLowerTriangular())).to.equal(true);(expect((z.matrix.new({{1,2},{3;4}})).isLowerTriangular())).to.equal(false);(expect((z.matrix.new({{1,2,3},{4;5;6}})).isLowerTriangular())).to.equal(false)end)it("\115\104\111\117\108\100 \099\097\108\099\117\108\097\116\101 \110\111\114\109\115 \099\111\114\114\101\099\116\108\121",function()local U=z.vector.new({1;2});(expect(z.vector.norm.l1(U))).to.equal(3);(expect(z.vector.norm.l2(U))).to.equal(math.sqrt(5));(expect(z.vector.norm.linf(U))).to.equal(2)end)it("\115\104\111\117\108\100 \099\097\108\099\117\108\097\116\101 \105\110\110\101\114 \112\114\111\100\117\099\116\115 \099\111\114\114\101\099\116\108\121",function()local U=z.matrix.identity(2)local Q=z.vector.new({1,2})local M=z.vector.new({2;1})local v=z.vector.new({1,2,3});(expect(z.vector.ip.dot(Q,M))).to.equal(4);(expect(function()local M=z.vector.ip.dot(Q,U)end)).to.throw();(expect(function()local U=z.vector.ip.dot(Q,v)end)).to.throw()end)it("\115\104\111\117\108\100 \104\097\110\100\108\101 \098\097\115\105\099 \118\101\099\116\111\114 \111\112\101\114\097\116\105\111\110\115 \099\111\114\114\101\099\116\108\121",function()local U=z.matrix.identity(2)local Q=z.vector.new({1;2})local M=z.vector.e(1,2)local v=z.vector.e(2,2)local n=z.vector.new({1,2;3});(expect(Q.project({M;v}))).to.equal(Q);(expect(Q.project(M))).to.equal(M);(expect(function()local z=Q.project(U)end)).to.throw();(expect(function()local z=Q.project(n)end)).to.throw();(expect(M.Unit)).to.equal(M)end)it("\115\104\111\117\108\100 \099\114\101\097\116\101 \112\114\111\112\101\114 \114\111\116\097\116\105\111\110 \109\097\116\114\105\099\101\115",function()local U=z.vector.e(1,3)local Q=z.vector.e(2,3)local M=U.createArbitraryAxisRotationMatrix(math.pi);(expect(M*U)).to.equal(U)do local z=M*Q;(expect(z[1][1])).to.equal(0);(expect(z[2][1])).to.be.near(-1);(expect(z[3][1])).to.be.near(0)end;(expect(function()local U=(z.vector.e(1,2)).createArbitraryAxisRotationMatrix(0)end)).to.throw()end)it("\115\104\111\117\108\100 \099\114\101\097\116\101 \097 \112\114\111\112\101\114 \111\114\116\104\111\110\111\114\109\097\108 \098\097\115\105\115",function()local U=z.vector.e(1,2)local Q=z.vector.e(2,2)do local M=z.gramSchmidt({U,Q});(expect(#M)).to.equal(2);(expect(M[1])).to.equal(U);(expect(M[2])).to.equal(Q)end do local M=z.gramSchmidt(U);(expect(#M)).to.equal(2);(expect(M[1])).to.equal(U);(expect(M[2])).to.equal(Q)end end)end
return function()
    local linalg = require(script.Parent.linalg)

    it("should have immutable matrices", function()
        local mat = linalg.matrix.new({
            {1, 2},
            {3, 4}
        })

        expect(function() mat[1] = 1 end).to.throw()
        expect(function() mat[1][1] = 1 end).to.throw()
    end)

    it("should error on unrecognized keys", function()
        local mat = linalg.matrix.new({
            {1, 2},
            {3, 4}
        })

        expect(function() local _ = mat["Invalid Key"] end).to.throw()
        expect(function() local _ = mat[1]["Invalid Key"] end).to.throw()
    end)

    it("should handle equality correctly", function()
        local mat = linalg.matrix.new({
            {1, 2},
            {3, 4}
        })
        local matT = linalg.matrix.new({
            {1, 3},
            {2, 4}
        })
        local mat2x3 = linalg.matrix.new({
            {1, 2, 3},
            {4, 5, 6}
        })

        expect(mat == mat).to.equal(true)
        expect(mat == matT).to.equal(false)
        expect(mat == mat2x3).to.equal(false)
        expect(mat[1] == mat[1]).to.equal(true)
        expect(mat[1] == matT[1]).to.equal(false)
        expect(mat[1] == mat2x3[1]).to.equal(false)
    end)

	it("should handle matrix and scalar arithmetic correctly", function()
		local mat = linalg.matrix.new({
            {1, 2},
            {3, 4}
        })
        local mat2x3 = linalg.matrix.new({
            {1, 2, 3},
            {4, 5, 6}
        })

		expect(-mat).to.equal(linalg.matrix.new({
            {-1, -2},
            {-3, -4}
        }))

        expect(function() local _ = 1 + mat end).to.throw()
        expect(function() local _ = mat + 2 end).to.throw()

        expect(function() local _ = 1 - mat end).to.throw()
        expect(function() local _ = mat - 2 end).to.throw()

        expect(2 * mat).to.equal(linalg.matrix.new({
            {2, 4},
            {6, 8}
        }))
        expect(2 * mat).to.equal(mat * 2)

        expect(mat / 2).to.equal(linalg.matrix.new({
            {0.5, 1},
            {1.5, 2}
        }))
        expect(function() local _ = 2 / mat end).to.throw()
        expect(function() local _ = mat / mat end).to.throw()

        expect(mat % 2).to.equal(linalg.matrix.new({
            {1, 0},
            {1, 0}
        }))
        expect(function() local _ = 1 % mat end).to.throw()
        expect(function() local _ = mat % mat end).to.throw()

        expect(mat ^ 2).to.equal(mat * mat)
        expect(function() local _ = mat2x3 ^ 2 end).to.throw()
        expect(function() local _ = mat ^ 2.5 end).to.throw()
        expect(function() local _ = 2 ^ mat end).to.throw()
        expect(function() local _ = mat ^ mat end).to.throw()
    end)

	it("should handle matrix and matrix arithmetic correctly", function()
		local mat = linalg.matrix.new({
            {1, 2},
            {3, 4}
        })

        expect(mat + mat).to.equal(mat * 2)
        expect(function() local _ = mat + linalg.matrix.new({ {1} }) end).to.throw()

        expect(mat - mat).to.equal(linalg.matrix.zeros(2, 2))
        expect(function() local _ = mat - linalg.matrix.new({ {1} }) end).to.throw()

        expect(mat * mat).to.equal(linalg.matrix.new({
            {7, 10},
            {15, 22}
        }))
        expect(function() local _ = mat * linalg.matrix.new({ {1} }) end).to.throw()
    end)

    it("should handle arithmetic on rows correctly", function()
        local mat = linalg.matrix.new({
            {1, 2},
            {3, 4}
        })
        local v = linalg.vector.new({ 1, 2 })

        expect(-(mat[1])).to.equal(linalg.matrix.new({ {-1, -2} })[1])
        expect(-v).to.equal(linalg.vector.new({ -1, -2 }))
        expect(-v[1]).to.equal(-1)

        expect(v[1] + 1).to.equal(2)
        expect(v[1] + v[1]).to.equal(2)
        expect(mat[1] + mat[1]).to.equal(linalg.matrix.new({ {2, 4} })[1])
        expect(function() local _ = mat[1] + 1 end).to.throw()
        expect(function() local _ = mat[1] + v[1] end).to.throw()

        expect(v[1] - 1).to.equal(0)
        expect(v[1] - v[1]).to.equal(0)
        expect(mat[1] - mat[1]).to.equal(linalg.matrix.new({ {0, 0} })[1])
        expect(function() local _ = mat[1] - 1 end).to.throw()
        expect(function() local _ = mat[1] - v[1] end).to.throw()

        expect(2 * mat[1]).to.equal(linalg.matrix.new({ {2, 4} })[1])
        expect(v[1] * mat[1]).to.equal(mat[1])
        expect(mat[1] * v[1]).to.equal(mat[1])
        expect(2 * v[1]).to.equal(2)
        expect(v[1] * 2).to.equal(2)
        expect(v[1] * v[1]).to.equal(1)
        expect(function() local _ = mat[1] * mat[1] end).to.throw()

        expect(mat[2] / 2).to.equal(linalg.matrix.new({ {1.5, 2} })[1])
        expect(mat[1] / v[1]).to.equal(mat[1])
        expect(v[2] / 2).to.equal(1)
        expect(v[1] / v[1]).to.equal(1)
        expect(function() local _ = 1 / mat[1] end).to.throw()
        expect(function() local _ = v[1] / mat[1] end).to.throw()
        expect(function() local _ = mat[1] / mat[1] end).to.throw()

        expect(mat[2] % 2).to.equal(linalg.matrix.new({ {1, 0} })[1])
        expect(mat[1] % v[2]).to.equal(linalg.matrix.new({ {1, 0} })[1])
        expect(v[1] % 2).to.equal(1)
        expect(v[1] % v[2]).to.equal(1)
        expect(function() local _ = 1 % mat[1] end).to.throw()
        expect(function() local _ = v[1] % mat[1] end).to.throw()
        expect(function() local _ = mat[1] % mat[1] end).to.throw()

        expect(2 ^ v[2]).to.equal(4)
        expect(v[2] ^ 2).to.equal(4)
        expect(v[2] ^ v[2]).to.equal(4)
        expect(function() local _ = 2 ^ mat[1] end).to.throw()
        expect(function() local _ = mat[1] ^ 2 end).to.throw()
        expect(function() local _ = v[2] ^ mat[1] end).to.throw()
        expect(function() local _ = mat[1] ^ v[2] end).to.throw()
        expect(function() local _ = mat[1] ^ mat[1] end).to.throw()
    end)

    it("should handle basic matrix operations correctly", function()
        local mat = linalg.matrix.new({
            {1, 2},
            {3, 4}
        })
        local mat2x3 = linalg.matrix.new({
            {1, 2, 3},
            {4, 5, 6}
        })

        expect(mat.T).to.equal(linalg.matrix.new({
            {1, 3},
            {2, 4}
        }))

        expect(mat.Unit).to.equal(linalg.matrix.new({
            {1, 0},
            {0, 1}
        }))
        expect(function() local _ = mat2x3.Unit end).to.throw()

        local expmResult = mat.expm()
        expect(expmResult[1][1]).to.be.near(51.968956198705)
        expect(expmResult[1][2]).to.be.near(74.736564567003)
        expect(expmResult[2][1]).to.be.near(112.1048468505)
        expect(expmResult[2][2]).to.be.near(164.07380304921)

        expect(function() local _ = linalg.matrix.new({
            {1, 2, 3},
            {4, 5, 6}
        }).expm() end).to.throw()
    end)

    it("should create matrices correctly", function()
        expect(linalg.matrix.fromColumns({{1, 3}, {2, 4}})).to.equal(linalg.matrix.new({
            {1, 2},
            {3, 4}
        }))
        expect(linalg.matrix.fromColumns({
            linalg.matrix.new({ {1}, {3} }),
            linalg.matrix.new({ {2}, {4} })
        })).to.equal(linalg.matrix.new({
            {1, 2},
            {3, 4}
        }))

        expect(linalg.matrix.identity(2)).to.equal(linalg.matrix.new({
            {1, 0},
            {0, 1}
        }))

        expect(linalg.matrix.zeros(2, 2)).to.equal(linalg.matrix.new({
            {0, 0},
            {0, 0}
        }))

        expect(linalg.matrix.zerosLike(linalg.matrix.identity(2))).to.equal(linalg.matrix.new({
            {0, 0},
            {0, 0}
        }))

        expect(linalg.matrix.diagonal({1, 2})).to.equal(linalg.matrix.new({
            {1, 0},
            {0, 2}
        }))
    end)

    it("should classify matrices correctly", function()
        expect(linalg.matrix.identity(2).isDiagonal()).to.equal(true)
        expect(linalg.matrix.new({
            {1, 0, 0},
            {0, 1, 0}
        }).isDiagonal()).to.equal(true)
        expect(linalg.matrix.new({
            {1, 2},
            {3, 4}
        }).isDiagonal()).to.equal(false)

        expect(linalg.matrix.identity(2).isUpperTriangular()).to.equal(true)
        expect(linalg.matrix.new({
            {1, 2},
            {3, 4}
        }).isUpperTriangular()).to.equal(false)
        expect(linalg.matrix.new({
            {1, 2, 3},
            {4, 5, 6}
        }).isUpperTriangular()).to.equal(false)

        expect(linalg.matrix.identity(2).isLowerTriangular()).to.equal(true)
        expect(linalg.matrix.new({
            {1, 2},
            {3, 4}
        }).isLowerTriangular()).to.equal(false)
        expect(linalg.matrix.new({
            {1, 2, 3},
            {4, 5, 6}
        }).isLowerTriangular()).to.equal(false)
    end)

    it("should calculate norms correctly", function()
        local v = linalg.vector.new({ 1, 2 })

        expect(linalg.vector.norm.l1(v)).to.equal(3)

        expect(linalg.vector.norm.l2(v)).to.equal(math.sqrt(5))

        expect(linalg.vector.norm.linf(v)).to.equal(2)
    end)

    it("should calculate inner products correctly", function()
        local I_2 = linalg.matrix.identity(2)
        local v1 = linalg.vector.new({ 1, 2 })
        local v2 = linalg.vector.new({ 2, 1 })
        local r3v = linalg.vector.new({ 1, 2, 3 })

        expect(linalg.vector.ip.dot(v1, v2)).to.equal(4)
        expect(function() local _ = linalg.vector.ip.dot(v1, I_2) end).to.throw()
        expect(function() local _ = linalg.vector.ip.dot(v1, r3v) end).to.throw()
    end)

    it("should handle basic vector operations correctly", function()
        local I_2 = linalg.matrix.identity(2)
        local v = linalg.vector.new({ 1, 2 })
        local e1 = linalg.vector.e(1, 2)
        local e2 = linalg.vector.e(2, 2)
        local r3v = linalg.vector.new({ 1, 2, 3 })

        expect(v.project({e1, e2})).to.equal(v)
        expect(v.project(e1)).to.equal(e1)
        expect(function() local _ = v.project(I_2) end).to.throw()
        expect(function() local _ = v.project(r3v) end).to.throw()

        expect(e1.Unit).to.equal(e1)
    end)

    it("should create proper rotation matrices", function()
        local e1 = linalg.vector.e(1, 3)
        local e2 = linalg.vector.e(2, 3)
        local rot180 = e1.createArbitraryAxisRotationMatrix(math.pi)

        expect(rot180 * e1).to.equal(e1)

        do
            local rotatedE2 = rot180 * e2
            expect(rotatedE2[1][1]).to.equal(0)
            expect(rotatedE2[2][1]).to.be.near(-1)
            expect(rotatedE2[3][1]).to.be.near(0)
        end

        expect(function() local _ = linalg.vector.e(1, 2).createArbitraryAxisRotationMatrix(0) end).to.throw()
    end)

    it("should create a proper orthonormal basis", function()
        local e1 = linalg.vector.e(1, 2)
        local e2 = linalg.vector.e(2, 2)

        do
            local basis = linalg.gramSchmidt({ e1, e2 })
            expect(#basis).to.equal(2)
            expect(basis[1]).to.equal(e1)
            expect(basis[2]).to.equal(e2)
        end

        do
            local basis = linalg.gramSchmidt(e1)
            expect(#basis).to.equal(2)
            expect(basis[1]).to.equal(e1)
            expect(basis[2]).to.equal(e2)
        end
    end)
end