File size: 2,791 Bytes
e215d3c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<?php

declare(strict_types=1);

namespace Dochia;

/**
 * Predictive distribution with body scale and separate tail stretching.
 *
 * The body (between tail anchors) is a lognormal mixture; the lower and upper
 * tails are stretched so that the 80% interval and the 95% interval can be
 * calibrated independently. This is a coherent piecewise distribution.
 */
final class CalibratedPredictive
{
    public function __construct(
        public readonly LognormalMixture $base,
        public readonly float $anchorLow,
        public readonly float $anchorHigh,
        public readonly float $scaleLow,
        public readonly float $scaleHigh,
        public readonly float $qLow,
        public readonly float $qHigh,
    ) {
    }

    public static function fromBase(LognormalMixture $base, ModelParams $params): self
    {
        return new self(
            base: $base,
            anchorLow: $params->tailAnchorLow,
            anchorHigh: $params->tailAnchorHigh,
            scaleLow: max($params->tailScaleLow, 1e-6),
            scaleHigh: max($params->tailScaleHigh, 1e-6),
            qLow: $base->quantile($params->tailAnchorLow),
            qHigh: $base->quantile($params->tailAnchorHigh),
        );
    }

    public function quantile(float $p): float
    {
        if ($p <= 0.0) {
            return 0.0;
        }
        if ($p >= 1.0) {
            return 1000.0;
        }
        if ($p <= $this->anchorLow) {
            return $this->qLow + ($this->base->quantile($p) - $this->qLow) * $this->scaleLow;
        }
        if ($p >= $this->anchorHigh) {
            return $this->qHigh + ($this->base->quantile($p) - $this->qHigh) * $this->scaleHigh;
        }
        return $this->base->quantile($p);
    }

    public function cdf(float $x): float
    {
        if ($x <= $this->qLow) {
            $y = $this->qLow + ($x - $this->qLow) / $this->scaleLow;
            return min(max($this->base->cdf($y), 0.0), 1.0);
        }
        if ($x >= $this->qHigh) {
            $y = $this->qHigh + ($x - $this->qHigh) / $this->scaleHigh;
            return min(max($this->base->cdf($y), 0.0), 1.0);
        }
        return $this->base->cdf($x);
    }

    public function pdf(float $x): float
    {
        if ($x <= 0.0) {
            return 0.0;
        }
        if ($x < $this->qLow) {
            return $this->base->pdf($this->qLow + ($x - $this->qLow) / $this->scaleLow) / $this->scaleLow;
        }
        if ($x > $this->qHigh) {
            return $this->base->pdf($this->qHigh + ($x - $this->qHigh) / $this->scaleHigh) / $this->scaleHigh;
        }
        return $this->base->pdf($x);
    }

    public function mean(): float
    {
        return $this->base->mean;
    }

    public function sd(): float
    {
        return $this->base->sd;
    }
}