8656feed创建于 2025年10月29日历史提交
/* Special matrices */

package scientific.linear.special

import std.unittest.*
import std.unittest.testmacro.*
import std.math.*

import scientific.numbers.CNumber
import scientific.numbers.Float
import scientific.numbers.Real
import scientific.numbers.power
import scientific.numbers.Complex64
import scientific.numbers.approxEqualC
import scientific.linear.Vector
import scientific.linear.vector
import scientific.linear.zeros
import scientific.linear.Matrix
import scientific.linear.matrix
import scientific.linear.empty
import scientific.linear.approxEqual
import scientific.linear.arange
import scientific.linear.diag
import scientific.linear.toComplex64
import scientific.linear.expm
import scientific.linear.hstack
import scientific.linear.vstack
import scientific.linear.*
import scientific.stats.utils.comb
import scientific.utils.AssertionException


public func vander<T>(x: Vector<T>, N: Int64, increasing!:Bool = false): Matrix<T> where T <: Float<T> {
    let sz = x.size()
    let res = empty<T>(sz, N)
    for (i in 0..sz) {
        for (j in 0..N) {
            if (increasing) {
                res[i,j] = power(x[i], T.fromInt(j))
            } else {
                res[i,j] = power(x[i], T.fromInt(N-1-j))
            }
        }
    }
    return res
}

public func circulant<T>(x: Vector<T>): Matrix<T> where T <: CNumber<T> {
    let sz = x.size()
    let res = empty<T>(sz, sz)
    for (i in 0..sz) {
        for (j in 0..sz) {
            if (i + j < sz) {
                res[i,j] = x[i+j]
            } else {
                res[i,j] = x[i+j-sz]
            }
        }
    }
    return res
}

public func companion<T>(x: Vector<T>): Matrix<T> where T <: Float<T> {
    let sz = x.size() - 1
    let res = zeros<T>(sz, sz)
    for (i in 0..sz) {
        res[0,i] = T.fromInt(-1) * x[i+1] / x[0]
    }
    for (i in 1..sz) {
        res[i,i-1] = T.fromInt(1)
    }
    return res
}

public func fiedler<T>(x: Vector<T>): Matrix<T> where T <: Real<T> {
    let sz = x.size()
    let res = zeros<T>(sz, sz)
    for (i in 0..sz) {
        for (j in 0..sz) {
            res[i,j] = T.abs(x[i] - x[j])
        }
    }
    return res
}

public func hankel<T>(x: Vector<T>): Matrix<T> where T <: CNumber<T> {
    let sz = x.size()
    let res = zeros<T>(sz, sz)
    for (i in 0..sz) {
        for (j in 0..i+1) {
            res[j,i-j] = x[i]
        }
    }
    return res
}

public func hankel<T>(x: Vector<T>, y: Vector<T>): Matrix<T> where T <: CNumber<T> {
    let sx = x.size()
    let sy = y.size()
    let res = empty<T>(sx, sy)
    for (i in 0..sx) {
        for (j in 0..i+1) {
            res[j,i-j] = x[i]
        }
    }
    for (i in 0..sx) {
        for (j in sx-i..sy) {
            res[i,j] = y[j+i-sx+1]
        }
    }
    return res
}

public func hilbert<T>(N: Int64): Matrix<T> where T <: Float<T> {
    let res = empty<T>(N, N)
    for (i in 0..N) {
        for (j in 0..N) {
            res[i,j] = T.fromInt(1) / T.fromInt(i + j + 1)
        }
    }
    return res
}

public func leslie<T>(f: Vector<T>, s: Vector<T>): Matrix<T> where T <: CNumber<T> {
    let sz = f.size()
    if (s.size() != sz - 1) {
        throw AssertionException("leslie: size of vectors does not match")
    }

    let res = zeros<T>(sz, sz)
    for (i in 0..sz) {
        res[0,i] = f[i]
    }
    for (i in 0..sz-1) {
        res[i+1,i] = s[i]
    }
    return res
}

public func pascal(n: Int64, kind!:String="symmetric"): Matrix<Int64> {
    let res = zeros<Int64>(n, n)
    for (i in 0..n) {
        for (j in 0..n) {
            if (kind == "symmetric") {
                res[i,j] = comb(i+j, i)
            } else if (kind == "lower") {
                res[i,j] = comb(i, j)
            } else if (kind == "upper") {
                res[i,j] = comb(j, i)
            } else {
                throw AssertionException("pascal: unrecognized kind")
            }
        }
    }
    return res
}

public func dft(n: Int64): Matrix<Complex64> {
    var x = toComplex64(arange<Float64>(0.0, Float64(n), 1.0).toMatrix())
    let real: Float64 = Float64.getPI() * 1.0 / Float64(n)
    var tempj = Complex64(real, 0.0)
    var temp: Matrix<Complex64> = Complex64(0.0, -2.0) * (tempj * x)
    var omegas = matExp(temp)
    var result = getExp(omegas)
    return result
}

public func blockDiag<T>(a: Array<Matrix<T>>): Matrix<T> where T <: CNumber<T> {
    let size = a.size
    var arr = zeros<Int64>(size, 2)
    var rowCount: Int64 = 0
    var colCount: Int64 = 0
    for (i in 0..size-1) {
        rowCount = rowCount + a[i].getRows()
        colCount = colCount + a[i].getCols()
        arr[i+1,0] = rowCount
        arr[i+1,1] = colCount
    }
    var res = zeros<T>(rowCount+1, colCount+1)
    for (i in 0..size) {
        for (j in 0..a[i].getRows()) {
            for(k in 0..a[i].getCols()) {
                res[arr[i,0]+j, arr[i,1]+k] = a[i][j,k]
            }
        }
    }
    return res
}

public func toeplitz<T>(c: Vector<T>, r: Vector<T>): Matrix<T> where T <: CNumber<T> {
    let row = c.size()
    let col = r.size()
    var res = zeros<T>(row, col)
    for (i in 0..row) {
        res[i,0] = c[i]
    }
    for (i in 0..col) {
        res[0,i] = r[i]
    }
    for (i in 0..col) {
        for (j in 0..row) {
            if (i + j < col) {
                res[j,i+j] = res[0,i]
            }
        }
    }
    for (i in 0..row) {
        for (j in 0..col) {
            if (i + j < row) {
                res[i+j,j] = res[i,0]
            }
        }
    }
    return res
}

public func convolutionMatrix<T>(a: Vector<T>, n: Int64, mode!:String="full"): Matrix<T> where T <: CNumber<T> {
    var m = a.size()
    var row: Int64 = 0
    if (mode == "full") {
        row = m + n - 1
    } else if (mode == "same") {
        row = max(m, n)
    } else if (mode == "valid") {
        row = max(m, n) - min(m, n) + 1
    }
    var col = n
    var res = zeros<T>(row, col)
    if (mode == "full") {
        var cvector = zeros<T>(row)
        var rvector = zeros<T>(col)
        for (i in 0..m) {
            cvector[i] = a[i]
        }
        rvector[0] = a[0]
        return toeplitz(cvector, rvector)
    } else if (mode == "same") {
        var cvector = zeros<T>(row)
        var rvector = zeros<T>(col)
        var mid = m / 2
        var temp = 0
        if (m % 2 == 0) {
            temp = mid - 1
        } else {
            temp = mid
        }
        for (i in 0..temp+1) {
            rvector[i] = a[temp-i]
        }
        for (i in 0..temp+1) {
            cvector[i] = a[temp+i]
        }
        return toeplitz(cvector, rvector)
    } else {
        var cvector = zeros<T>(row)
        var rvector = zeros<T>(col)
        for (i in 0..m) {
            rvector[i] = a[m-i-1]
        }
        cvector[0] = a[m-1]
        return toeplitz(cvector, rvector)
    }
}

public func hadamard(a: Int64): Matrix<Int64> {
    var H = matrix<Int64>([[1]])
    var lg2 = Int64(log2(Float64(a)))
    for (i in 0..lg2) {
        var MH = H * (-1) 
        var temp = hstack(H, H)
        var temp2 = hstack(H, MH)
        H = vstack(temp, temp2)
    }
    return H
}

public func helmert(n: Int64): Matrix<Float64> {
    var temp = zeros<Int64>(n, n)
    for (i in 0..n) {
        for (j in 0..n) {
            temp[i,j] = 1
        }
    }
    var H = tril(temp, k: -1) - diag(arange(0, n, 1))
    var d = arange(0, n, 1) * arange(1, n+1, 1)
    d[0] = n
    for (j in 0..n) {
        H[0,j] = 1
    }
    var res = zeros<Float64>(n, n)
    for (i in 0..n){
        for (j in 0..n) {
            res[i,j] = Float64(H[i,j]) / sqrt(Float64(d[i]))
        }
    }
    return res
}


@Test
public class TestSpecialMatrix {
    @TestCase
    func testDft2(): Unit {
        let expected_dft = matrix<Complex64>([
            [Complex64(1.0, 0.0), Complex64(1.0, 0.0), Complex64(1.0, 0.0), Complex64(1.0, 0.0), Complex64(1.0, 0.0)],
            [Complex64(1.0, 0.0), Complex64(0.309017, -0.951057), Complex64(-0.809017, -0.587785), Complex64(-0.809017, 0.587785), Complex64(0.309017, 0.951057)],
            [Complex64(1.0, 0.0), Complex64(-0.809017, -0.587785), Complex64(0.309017, 0.951057), Complex64(0.309017, -0.951057), Complex64(-0.809017, 0.587785)],
            [Complex64(1.0, 0.0), Complex64(-0.809017, 0.587785), Complex64(0.309017, -0.951057), Complex64(0.309017, 0.951057), Complex64(-0.809017, -0.587785)],
            [Complex64(1.0, 0.0), Complex64(0.309017, 0.951057), Complex64(-0.809017, 0.587785), Complex64(-0.809017, -0.587785), Complex64(0.309017, -0.951057)]
        ])
        @Assert(approxEqualC(dft(5), expected_dft, atol:1e-6))
    }

    @TestCase
    func testBlockDiag2(): Unit {
        var A = matrix<Int64>(
            [[1, 0],
             [0, 1]]
        )
        var B = matrix<Int64>(
            [[3, 4, 5],
             [6, 7, 8]]
        )
        var C = matrix<Int64>(
            [[7]]
        )
        var arr = [A, B, C]

        let expected_res = matrix<Int64>(
            [[1, 0, 0, 0, 0, 0],
             [0, 1, 0, 0, 0, 0],
             [0, 0, 3, 4, 5, 0],
             [0, 0, 6, 7, 8, 0],
             [0, 0, 0, 0, 0, 7]
        ])
        
        @Assert(blockDiag(arr) == expected_res)
    }

    @TestCase
    func testVander(): Unit {
        let x = vector<Float64>([1.0, 2.0, 3.0, 5.0])
        let expected_res = matrix<Float64>([
            [1.0, 1.0, 1.0],
            [4.0, 2.0, 1.0],
            [9.0, 3.0, 1.0],
            [25.0, 5.0, 1.0]
        ])
        @Assert(approxEqual(vander(x, 3), expected_res))
    }

    @TestCase
    func testVanderIncreasing(): Unit {
        let x = vector<Float64>([1.0, 2.0, 3.0, 5.0])
        let expected_res = matrix<Float64>([
            [1.0, 1.0, 1.0],
            [1.0, 2.0, 4.0],
            [1.0, 3.0, 9.0],
            [1.0, 5.0, 25.0]
        ])
        @Assert(approxEqual(vander(x, 3, increasing:true), expected_res))
    }

    @TestCase
    func testCirculant(): Unit {
        let x = vector<Float64>([1.0, 2.0, 3.0])
        let expected_res = matrix<Float64>([
            [1.0, 2.0, 3.0],
            [2.0, 3.0, 1.0],
            [3.0, 1.0, 2.0]
        ])
        @Assert(approxEqual(circulant(x), expected_res))
    }

    @TestCase
    func testCompanion(): Unit {
        let x = vector<Float64>([1.0, -10.0, 31.0, -30.0])
        let expected_res = matrix<Float64>([
            [10.0, -31.0, 30.0],
            [1.0, 0.0, 0.0],
            [0.0, 1.0, 0.0]
        ])
        @Assert(approxEqual(companion(x), expected_res))
    }

    @TestCase
    func testCompanion2(): Unit {
        let x = vector<Float64>([2.0, -10.0, 31.0, -30.0])
        let expected_res = matrix<Float64>([
            [5.0, -15.5, 15.0],
            [1.0, 0.0, 0.0],
            [0.0, 1.0, 0.0]
        ])
        @Assert(approxEqual(companion(x), expected_res))
    }

    @TestCase
    func testFiedler(): Unit {
        let x = vector<Int64>([1, 4, 12, 45, 77])
        let expected_res = matrix<Int64>([
            [0, 3, 11, 44, 76],
            [3, 0, 8, 41, 73],
            [11, 8, 0, 33, 65],
            [44, 41, 33, 0, 32],
            [76, 73, 65, 32, 0]
        ])
        @Assert(fiedler(x) == expected_res)
    }

    @TestCase
    func testHankel(): Unit {
        let x = vector<Int64>([1, 17, 99])
        let expected_res = matrix<Int64>([
            [1, 17, 99],
            [17, 99, 0],
            [99, 0, 0]
        ])
        @Assert(hankel(x) == expected_res)
    }

    @TestCase
    func testHankel2(): Unit {
        let x = vector<Int64>([1, 2, 3, 4])
        let y = vector<Int64>([4, 7, 7, 8, 9])
        let expected_res = matrix<Int64>([
            [1, 2, 3, 4, 7],
            [2, 3, 4, 7, 7],
            [3, 4, 7, 7, 8],
            [4, 7, 7, 8, 9]
        ])
        @Assert(hankel(x, y) == expected_res)
    }

    @TestCase
    func testHilbert(): Unit {
        let expected_res = matrix<Float64>([
            [1.000000, 0.500000, 0.333333],
            [0.500000, 0.333333, 0.250000],
            [0.333333, 0.250000, 0.200000]
        ])
        @Assert(approxEqual(hilbert<Float64>(3), expected_res, atol:1e-6))
    }

    @TestCase
    func testLeslie(): Unit {
        let f = vector<Float64>([0.1, 2.0, 1.0, 0.1])
        let s = vector<Float64>([0.2, 0.8, 0.7])
        let expected_res = matrix<Float64>([
            [0.1, 2.0, 1.0, 0.1],
            [0.2, 0.0, 0.0, 0.0],
            [0.0, 0.8, 0.0, 0.0],
            [0.0, 0.0, 0.7, 0.0]
        ])
        @Assert(approxEqual(leslie(f, s), expected_res))
    }

    @TestCase
    func testPascal(): Unit {
        let expected_res = matrix<Int64>([
            [1, 1, 1, 1],
            [1, 2, 3, 4],
            [1, 3, 6, 10],
            [1, 4, 10, 20]
        ])
        @Assert(pascal(4) == expected_res)
    }

    @TestCase
    func testPascalLower(): Unit {
        let expected_res = matrix<Int64>([
            [1, 0, 0, 0],
            [1, 1, 0, 0],
            [1, 2, 1, 0],
            [1, 3, 3, 1]
        ])
        @Assert(pascal(4, kind:"lower") == expected_res)
    }

    @TestCase
    func testPascalUpper(): Unit {
        let expected_res = matrix<Int64>([
            [1, 1, 1, 1],
            [0, 1, 2, 3],
            [0, 0, 1, 3],
            [0, 0, 0, 1]
        ])
        @Assert(pascal(4, kind:"upper") == expected_res)
    }

    // @TestCase
    // func testHelmert(): Unit {
    //     var res = helmert(5)
    //     var expected_res = matrix<Float64>([
    //         [0.447214, 0.447214, 0.447214, 0.447214, 0.447214],
    //         [0.707107, -0.707107, 0.000000, 0.000000, 0.000000],
    //         [0.408248, 0.408248, -0.816497, 0.000000, 0.000000],
    //         [0.288675, 0.288675, 0.288675, -0.866025, 0.000000],
    //         [0.223607, 0.223607, 0.223607, 0.223607, -0.894427]
    //     ])
    //     @Assert(approxEqual(res, expected_res, atol:1e-6))
    // }

    @TestCase
    func testHadamard(): Unit {
        var res = hadamard(4)
        let expected_res = matrix<Int64>([
            [1, 1, 1, 1],
            [1, -1, 1, -1],
            [1, 1, -1, -1],
            [1, -1, -1, 1]
        ])
        @Assert(res == expected_res)
    }

    @TestCase
    func testConvolutionMatrix(): Unit {
        let a = vector<Int64>([-1, 4, -2])
        var res = convolutionMatrix(a, 5)
        let expected_res = matrix<Int64>([
            [-1, 0, 0, 0, 0],
            [ 4, -1, 0, 0, 0],
            [-2, 4, -1, 0, 0],
            [ 0, -2, 4, -1, 0],
            [ 0, 0, -2, 4, -1],
            [ 0, 0, 0, -2, 4],
            [ 0, 0, 0, 0, -2]
        ])
        @Assert(res == expected_res)
    }

    @TestCase
    func testConvolutionMatrixValid(): Unit {
        let a = vector<Int64>([-1, 4, -2])
        var res = convolutionMatrix(a, 5, mode:"valid")
        let expected_res = matrix<Int64>([
            [-2, 4, -1, 0, 0],
            [ 0, -2, 4, -1, 0],
            [ 0, 0, -2, 4, -1]
        ])
        @Assert(res == expected_res)
    }

    @TestCase
    func testConvolutionMatrixSame(): Unit {
        let a = vector<Int64>([-1, 4, -2])
        var res = convolutionMatrix(a, 5, mode:"same")
        let expected_res = matrix<Int64>([
            [4, -1, 0, 0, 0],
            [-2, 4, -1, 0, 0],
            [ 0, -2, 4, -1, 0],
            [0, 0, -2, 4, -1],
            [ 0, 0, 0, -2, 4]
        ])
        @Assert(res == expected_res)
    }

    @TestCase
    func testToeplitz(): Unit {
        let a = vector<Int64>([1, 2, 3])
        let b = vector<Int64>([1, 4, 5, 6])
        var res = toeplitz(a, b)
        let expected_res = matrix<Int64>([
            [1, 4, 5, 6],
            [2, 1, 4, 5],
            [3, 2, 1, 4]
        ])
        @Assert(res == expected_res)
    }
}