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

Exponential::cdf()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 8.439
c 0
b 0
f 0
cc 5
eloc 16
nc 3
nop 1
1
<?php
2
3
namespace Samsara\Fermat\Provider\Stats\Distribution;
4
5
use RandomLib\Factory;
6
use Samsara\Exceptions\UsageError\IntegrityConstraint;
7
use Samsara\Exceptions\UsageError\OptionalExit;
8
use Samsara\Fermat\Numbers;
9
use Samsara\Fermat\Types\Base\DecimalInterface;
10
use Samsara\Fermat\Values\ImmutableNumber;
11
12
class Exponential
13
{
14
15
    /**
16
     * @var ImmutableNumber
17
     */
18
    private $lambda;
19
20
    /**
21
     * Exponential constructor.
22
     *
23
     * @param int|float|DecimalInterface $lambda This is the *rate parameter* not the *scale parameter*
24
     *
25
     * @throws IntegrityConstraint
26
     */
27 View Code Duplication
    public function __construct($lambda)
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...
28
    {
29
        $lambda = Numbers::makeOrDont(Numbers::IMMUTABLE, $lambda);
0 ignored issues
show
Bug introduced by
It seems like $lambda defined by \Samsara\Fermat\Numbers:...rs::IMMUTABLE, $lambda) on line 29 can also be of type object<Samsara\Fermat\Ty...\Base\DecimalInterface>; however, Samsara\Fermat\Numbers::makeOrDont() does only seem to accept integer|double|string|ob...s\Base\NumberInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
30
31
        if (!$lambda->isPositive()) {
32
            throw new IntegrityConstraint(
33
                'Lambda must be positive',
34
                'Provide a positive lambda',
35
                'Exponential distributions work on time to occurrence; the mean time to occurrence (lambda) must be positive'
36
            );
37
        }
38
39
        $this->lambda = $lambda;
0 ignored issues
show
Documentation Bug introduced by
It seems like $lambda of type object<Samsara\Fermat\Types\Base\NumberInterface> or array is incompatible with the declared type object<Samsara\Fermat\Values\ImmutableNumber> of property $lambda.

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...
40
    }
41
42
    /**
43
     * @param int|float|DecimalInterface $x
44
     *
45
     * @return ImmutableNumber
46
     * @throws IntegrityConstraint
47
     */
48
    public function cdf($x)
49
    {
50
51
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
0 ignored issues
show
Bug introduced by
It seems like $x defined by \Samsara\Fermat\Numbers:...Numbers::IMMUTABLE, $x) on line 51 can also be of type object<Samsara\Fermat\Ty...\Base\DecimalInterface>; however, Samsara\Fermat\Numbers::makeOrDont() does only seem to accept integer|double|string|ob...s\Base\NumberInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
52
53
        if (!$x->isPositive()) {
54
            throw new IntegrityConstraint(
55
                'X must be positive',
56
                'Provide a positive x',
57
                'Exponential distributions work on time to occurrence; the time to occurrence (x) must be positive'
58
            );
59
        }
60
61
        if (
62
            function_exists('stats_cdf_exponential') &&
63
            $x->isLessThanOrEqualTo(PHP_INT_MAX) &&
64
            $this->lambda->isLessThanOrEqualTo(PHP_INT_MAX)
65
        ) {
66
            return Numbers::make(Numbers::IMMUTABLE, stats_cdf_exponential($x->getValue(), $this->lambda->getValue(), 1));
67
        }
68
69
        /** @var ImmutableNumber $one */
70
        $one = Numbers::makeOne();
71
        /** @var ImmutableNumber $e */
72
        $e = Numbers::makeE();
73
74
        /** @var ImmutableNumber $cdf */
75
        $cdf = $one->subtract($e->pow($x->multiply($this->lambda)->multiply(-1)));
76
77
        return $cdf;
78
79
    }
80
81
    /**
82
     * @param $x
83
     *
84
     * @return ImmutableNumber
85
     * @throws IntegrityConstraint
86
     */
87 View Code Duplication
    public function pdf($x)
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...
88
    {
89
90
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
91
92
        if (!$x->isPositive()) {
93
            throw new IntegrityConstraint(
94
                'X must be positive',
95
                'Provide a positive x',
96
                'Exponential distributions work on time to occurrence; the time to occurrence (x) must be positive'
97
            );
98
        }
99
100
        if (
101
            function_exists('stats_dens_exponential') &&
102
            $x->isLessThanOrEqualTo(PHP_INT_MAX) &&
103
            $this->lambda->isLessThanOrEqualTo(PHP_INT_MAX)
104
        ) {
105
            return Numbers::make(Numbers::IMMUTABLE, stats_dens_exponential($x->getValue(), $this->lambda->pow(-1)->getValue()));
106
        }
107
108
        /** @var ImmutableNumber $e */
109
        $e = Numbers::makeE();
110
111
        /** @var ImmutableNumber $pdf */
112
        $pdf = $this->lambda->multiply($e->pow($this->lambda->multiply(-1)->multiply($x)));
113
114
        return $pdf;
115
116
    }
117
118
    /**
119
     * @param $x1
120
     * @param $x2
121
     *
122
     * @return ImmutableNumber
123
     * @throws IntegrityConstraint
124
     */
125
    public function rangePdf($x1, $x2)
126
    {
127
        $x1 = Numbers::makeOrDont(Numbers::IMMUTABLE, $x1);
128
        $x2 = Numbers::makeOrDont(Numbers::IMMUTABLE, $x2);
129
        $one = Numbers::makeOne();
130
131
        if (!$x1->isPositive() || !$x2->isPositive()) {
132
            throw new IntegrityConstraint(
133
                'X must be positive',
134
                'Provide a positive x',
135
                'Exponential distributions work on time to occurrence; the time to occurrence (x) must be positive'
136
            );
137
        }
138
139
        if (
140
            function_exists('stats_dens_exponential') &&
141
            $x1->isLessThanOrEqualTo(PHP_INT_MAX) &&
142
            $x2->isLessThanOrEqualTo(PHP_INT_MAX) &&
143
            $this->lambda->isLessThanOrEqualTo(PHP_INT_MAX)
144
        ) {
145
            /** @var ImmutableNumber $pdf1 */
146
            $pdf1 = Numbers::make(Numbers::IMMUTABLE, stats_dens_exponential($x1->getValue(), $one->divide($this->lambda)->getValue()));
147
            /** @var ImmutableNumber $pdf2 */
148
            $pdf2 = Numbers::make(Numbers::IMMUTABLE, stats_dens_exponential($x2->getValue(), $one->divide($this->lambda)->getValue()));
149
150
            /** @var ImmutableNumber $rangePdf */
151
            $rangePdf = $pdf1->subtract($pdf2)->abs();
152
153
            return $rangePdf;
154
        }
155
156
        /** @var ImmutableNumber $rangePdf */
157
        $rangePdf = $this->pdf($x2)->subtract($this->pdf($x1))->abs();
158
159
        return $rangePdf;
160
    }
161
162
    /**
163
     * @return ImmutableNumber
164
     */
165
    public function random()
166
    {
167
168
        $randFactory = new Factory();
169
        $generator = $randFactory->getMediumStrengthGenerator();
170
        $one = Numbers::makeOne();
171
        $u = $generator->generateInt() / PHP_INT_MAX;
172
173
        /** @var ImmutableNumber $random */
174
        $random = $one->subtract($u)->ln()->divide($this->lambda->multiply(-1));
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 ln() 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...
175
176
        return $random;
177
178
    }
179
180
    /**
181
     * @param int|float|DecimalInterface $min
182
     * @param int|float|DecimalInterface $max
183
     * @param int $maxIterations
184
     *
185
     * @return ImmutableNumber
186
     * @throws OptionalExit
187
     */
188 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...
189
    {
190
191
        $i = 0;
192
193
        do {
194
            $randomNumber = $this->random();
195
            $i++;
196
        } while (($randomNumber->isGreaterThanOrEqualTo($max) || $randomNumber->isLessThanOrEqualTo($min)) && $i < $maxIterations);
197
198
        if ($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) {
199
            throw new OptionalExit(
200
                'All random numbers generated were outside of the requested range',
201
                'A suitable random number, restricted by the $max ('.$max.') and $min ('.$min.'), could not be found within '.$maxIterations.' iterations'
202
            );
203
        } else {
204
            return $randomNumber;
205
        }
206
207
    }
208
209
}