Completed
Push — dev ( 021c9f...a2bcd2 )
by Jordan
02:02
created

Normal::xFromZScore()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace Samsara\Fermat\Provider\Stats\Distribution;
4
5
use RandomLib\Factory;
6
use Samsara\Exceptions\UsageError\OptionalExit;
7
use Samsara\Fermat\Numbers;
8
use Samsara\Fermat\Provider\Stats\Stats;
9
use Samsara\Fermat\Types\Base\NumberInterface;
10
use Samsara\Fermat\Values\ImmutableNumber;
11
12
class Normal
13
{
14
15
    /**
16
     * @var ImmutableNumber
17
     */
18
    private $mean;
19
20
    /**
21
     * @var ImmutableNumber
22
     */
23
    private $sd;
24
25
    public function __construct($mean, $sd)
26
    {
27
        $mean = Numbers::makeOrDont(Numbers::IMMUTABLE, $mean);
28
        $sd = Numbers::makeOrDont(Numbers::IMMUTABLE, $sd);
29
30
        $this->mean = $mean;
0 ignored issues
show
Documentation Bug introduced by
It seems like $mean of type object<Samsara\Fermat\Types\Base\NumberInterface> or array is incompatible with the declared type object<Samsara\Fermat\Values\ImmutableNumber> of property $mean.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
31
        $this->sd = $sd;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sd of type object<Samsara\Fermat\Types\Base\NumberInterface> or array is incompatible with the declared type object<Samsara\Fermat\Values\ImmutableNumber> of property $sd.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
32
    }
33
34 View Code Duplication
    public static function makeFromMean($p, $x, $mean)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
35
    {
36
        $one = Numbers::makeOne();
37
        $p = Numbers::makeOrDont(Numbers::IMMUTABLE, $p);
38
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
39
        $mean = Numbers::makeOrDont(Numbers::IMMUTABLE, $mean);
40
41
        $z = Stats::inverseNormalCDF($one->subtract($p));
42
43
        $sd = $x->subtract($mean)->divide($z);
44
45
        return new Normal($mean, $sd);
46
    }
47
48 View Code Duplication
    public static function makeFromSd($p, $x, $sd)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
49
    {
50
        $one = Numbers::makeOne();
51
        $p = Numbers::makeOrDont(Numbers::IMMUTABLE, $p);
52
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
53
        $sd = Numbers::makeOrDont(Numbers::IMMUTABLE, $sd);
54
55
        $z = Stats::inverseNormalCDF($one->subtract($p));
56
57
        $mean = $x->subtract($z->multiply($sd));
58
59
        return new Normal($mean, $sd);
60
    }
61
    
62
    public function cdf($x)
63
    {
64
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
65
66
        if (
67
            function_exists('stats_cdf_normal') &&
68
            $x->isLessThanOrEqualTo(PHP_INT_MAX) &&
69
            $x->isGreaterThanOrEqualTo(PHP_INT_MIN) &&
70
            $this->mean->isLessThanOrEqualTo(PHP_INT_MAX) &&
71
            $this->mean->isGreaterThanOrEqualTo(PHP_INT_MIN) &&
72
            $this->sd->isLessThanOrEqualTo(PHP_INT_MAX) &&
73
            $this->sd->isGreaterThanOrEqualTo(PHP_INT_MIN)
74
        ) {
75
            return Numbers::make(Numbers::IMMUTABLE, stats_cdf_normal($x->getValue(), $this->mean, $this->sd));
76
        }
77
78
        $oneHalf = Numbers::make(Numbers::IMMUTABLE, '0.5');
79
        $one = Numbers::makeOne();
80
        $sqrtTwo = Numbers::make(Numbers::IMMUTABLE, 2)->sqrt();
81
82
        return $oneHalf->multiply($one->add(Stats::gaussErrorFunction(
83
            $x->subtract($this->mean)->divide($this->sd->multiply($sqrtTwo))
84
        )));
85
    }
86
    
87
    public function pdf($x1, $x2 = null)
88
    {
89
90
        $x1 = Numbers::makeOrDont(Numbers::IMMUTABLE, $x1);
91
92
        if (is_null($x2)) {
93
            $separation = $x1->subtract($this->mean)->multiply(2)->abs();
94
95
            if ($this->mean->isLessThan($x1)) {
96
                $x2 = $x1->subtract($separation);
97
            } else {
98
                $x2 = $x1->add($separation);
99
            }
100
        }
101
102
        return $this->cdf($x1)->subtract($this->cdf($x2))->abs();
103
    }
104
105
    public function percentBelowX($x)
106
    {
107
        return $this->cdf($x);
108
    }
109
110
    public function percentAboveX($x)
111
    {
112
        $one = Numbers::makeOne();
113
114
        return $one->subtract($this->cdf($x));
115
    }
116
117
    public function zScoreOfX($x)
118
    {
119
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
120
121
        return $x->subtract($this->mean)->divide($this->sd);
122
    }
123
124
    public function xFromZScore($z)
125
    {
126
        $z = Numbers::makeOrDont(Numbers::IMMUTABLE, $z);
127
128
        return $z->multiply($this->sd)->add($this->mean);
129
    }
130
131
    /**
132
     * @return ImmutableNumber
133
     */
134
    public function random()
135
    {
136
        if (
137
            function_exists('stats_rand_gen_normal') &&
138
            $this->mean->isLessThanOrEqualTo(PHP_INT_MAX) &&
139
            $this->mean->isGreaterThanOrEqualTo(PHP_INT_MIN) &&
140
            $this->sd->isLessThanOrEqualTo(PHP_INT_MAX) &&
141
            $this->sd->isGreaterThanOrEqualTo(PHP_INT_MIN)
142
        ) {
143
            return Numbers::make(Numbers::IMMUTABLE, stats_rand_gen_normal($this->mean, $this->sd), 20);
144
        } else {
145
            $randFactory = new Factory();
146
            $generator = $randFactory->getMediumStrengthGenerator();
147
148
            $rand1 = Numbers::make(Numbers::IMMUTABLE, $generator->generateInt(), 20);
149
            $rand1 = $rand1->divide(PHP_INT_MAX);
150
            $rand2 = Numbers::make(Numbers::IMMUTABLE, $generator->generateInt(), 20);
151
            $rand2 = $rand2->divide(PHP_INT_MAX);
152
153
            $randomNumber = $rand1->ln()->multiply(-2)->sqrt()->multiply($rand2->multiply(Numbers::TAU)->cos(1, 2, 20));
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Samsara\Fermat\Types\Base\NumberInterface as the method cos() does only exist in the following implementations of said interface: Samsara\Fermat\Values\Currency, Samsara\Fermat\Values\ImmutableNumber, Samsara\Fermat\Values\MutableNumber.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
154
            $randomNumber = $randomNumber->multiply($this->sd)->add($this->mean);
155
156
            return $randomNumber;
157
        }
158
    }
159
160
    /**
161
     * @param int|float|NumberInterface $min
162
     * @param int|float|NumberInterface $max
163
     * @param int $maxIterations
164
     *
165
     * @return ImmutableNumber
166
     * @throws OptionalExit
167
     */
168 View Code Duplication
    public function rangeRandom($min = 0, $max = PHP_INT_MAX, $maxIterations = 20)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
169
    {
170
        $i = 0;
171
172
        do {
173
            $randomNumber = $this->random();
174
            $i++;
175
        } while (($randomNumber->isGreaterThanOrEqualTo($max) || $randomNumber->isLessThanOrEqualTo($min)) && $i < $maxIterations);
176
177
        if ($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) {
178
            throw new OptionalExit(
179
                'All random numbers generated were outside of the requested range',
180
                'A suitable random number, restricted by the $max ('.$max.') and $min ('.$min.'), could not be found within '.$maxIterations.' iterations'
181
            );
182
        } else {
183
            return $randomNumber;
184
        }
185
    }
186
187
}