eaebec39创建于 2024年10月24日历史提交
package scientific.stats.continuous

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

import scientific.numbers.*
import scientific.stats.random.*

/*
 * Log of Probability density function
 */
public func trapezoidLogPDF(x: Float64, a: Float64, c: Float64, loc!: Float64 = 0.0, scale!: Float64 = 1.0): Float64 {
    let y = (x - loc) / scale
    var paramCheck = true
    if (0.0 <= a && a <= 1.0 && 0.0 <= c && c <= 1.0 && a <= c) {
        paramCheck = false
    }
    if (paramCheck) {
        throw IllegalArgumentException("trapezoidLogPDF: shape parameter out of bound.")
    }

    let temp = trapezoidPDF(x, a, c, loc:loc, scale: scale)
    if (temp < 0.000001) {
        throw IllegalArgumentException("trapezoidLogPDF: return-value too small.")
    }

    return log(temp)
}

/*
 * Probability density function
 */
public func trapezoidPDF(x: Float64, a: Float64, c: Float64, loc!: Float64 = 0.0, scale!: Float64 = 1.0): Float64 {
    let y = (x - loc) / scale
    var paramCheck = true
    if (0.0 <= a && a <= 1.0 && 0.0 <= c && c <= 1.0 && a <= c) {
        paramCheck = false
    }
    if (paramCheck) {
        throw IllegalArgumentException("trapezoidPDF: shape parameter out of bound.")
    }

    let t = 2.0 / (c - a + 1.0)
    var res = 0.0
    if (y < a) {
        res = t * y / a
    } else if (a <= y && y <= c) {
        res = t
    } else {
        res = t * (1.0 - y) / (1.0 - c)
    }

    return res / scale
}

/*
 * Cumulative probability density function
 */
public func trapezoidCDF(x: Float64, a: Float64, c: Float64, loc!: Float64 = 0.0, scale!: Float64 = 1.0): Float64 {
    let y = (x - loc) / scale
    var paramCheck = true
    if (0.0 <= a && a <= 1.0 && 0.0 <= c && c <= 1.0 && a <= c) {
        paramCheck = false
    }
    if (paramCheck) {
        throw IllegalArgumentException("trapezoidLogCDF: shape parameter out of bound.")
    }

    var res = 0.0
    if (y < a) {
        res = y * y / a /(c - a + 1.0)
    } else if (a <= y && y <= c) {
        res = (a + 2.0 * (y - a)) / (c - a + 1.0)
    } else {
        res = 1.0 - ((1.0 - y) * (1.0 - y) / (c - a + 1.0) / (1.0 - c))
    }
    return res
}

/*
 * Log of Cumulative probability density function
 */
public func trapezoidLogCDF(x: Float64, a: Float64, c: Float64, loc!: Float64 = 0.0, scale!: Float64 = 1.0): Float64 {
    let y = (x - loc) / scale
    var paramCheck = true
    if (0.0 <= a && a <= 1.0 && 0.0 <= c && c <= 1.0 && a <= c) {
        paramCheck = false
    }
    if (paramCheck) {
        throw IllegalArgumentException("trapezoidCDF: shape parameter out of bound.")
    }

    let temp = trapezoidCDF(x, a, c, loc:loc, scale: scale)
    if (temp < 0.000001) {
        throw IllegalArgumentException("trapezoidCDF: return-value too small.")
    }

    return log(temp)
}

/*
 * PPF
 */
public func trapezoidPPF(q: Float64, a: Float64, c: Float64, loc!: Float64 = 0.0, scale!: Float64 = 1.0): Float64 {
    var paramCheck = true
    if (0.0 <= a && a <= 1.0 && 0.0 <= c && c <= 1.0 && a <= c) {
        paramCheck = false
    }
    if (paramCheck) {
        throw IllegalArgumentException("trapezoidPPF: shape parameter out of bound.")
    }
    
    if (q <= 0.0 || q >= 1.0) {
        throw IllegalArgumentException("trapezoidPPF: quantile out of bound.")
    }

    let ta = trapezoidCDF(a, a, c)
    let tc = trapezoidCDF(c, a, c)
    var res = 0.0
    if (q < ta) {
        res = sqrt(q * a * (c - a + 1.0))
    } else if (q <= tc) {
        res = 0.5 * q * (c - a + 1.0) + 0.5 * a
    } else {
        res = 1.0 - sqrt((1.0 - q) * (c - a + 1.0) * (1.0 - c))
    }

    return res * scale + loc
}

@Test
public class TestTrapezoid {
    @TestCase
    func testTrapezoid(): Unit {
        @Assert(approxEqual(trapezoidLogPDF(2.0, 0.2, 0.5, loc: 1.0, scale: 2.0), -0.2623642644674911,  atol:1e-13))
        @Assert(approxEqual(trapezoidLogCDF(2.0, 0.2, 0.5, loc: 1.0, scale: 2.0), -0.48550781578170077, atol:1e-13))
        @Assert(approxEqual(trapezoidPPF(0.2, 0.2, 0.5, loc: 1.0, scale: 2.0),     1.46,                atol:1e-6))
    }
}