Vector::divideByScalar()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
namespace Nubs\Vectorix;
3
4
use Exception;
5
6
/**
7
 * This class represents an immutable Euclidean vector and its associated
8
 * operations.
9
 *
10
 * Instances of this class will not change state.  Any operations on the vector
11
 * will return a new vector with the new state.
12
 */
13
class Vector
14
{
15
    /** @type array<int|float> The components of the vector. */
16
    protected $_components;
17
18
    /**
19
     * Initialize the vector with its components.
20
     *
21
     * @api
22
     * @param array<int|float> $components The components of the vector.
23
     */
24
    public function __construct(array $components)
25
    {
26
        $this->_components = $components;
27
    }
28
29
    /**
30
     * Creates a null/zero-length vector of the given dimension.
31
     *
32
     * @api
33
     * @param int $dimension The dimension of the vector to create.  Must be at least 0.
34
     * @return self The zero-length vector for the given dimension.
35
     * @throws Exception if the dimension is less than zero.
36
     */
37
    public static function nullVector($dimension)
38
    {
39
        if ($dimension < 0) {
40
            throw new Exception('Dimension must be zero or greater');
41
        }
42
43
        if ($dimension === 0) {
44
            return new static([]);
45
        }
46
47
        return new static(array_fill(0, $dimension, 0));
48
    }
49
50
    /**
51
     * Get the components of the vector.
52
     *
53
     * @api
54
     * @return array<int|float> The components of the vector.
55
     */
56
    public function components()
57
    {
58
        return $this->_components;
59
    }
60
61
    /**
62
     * Get the dimension/cardinality of the vector.
63
     *
64
     * @api
65
     * @return int The dimension/cardinality of the vector.
66
     */
67
    public function dimension()
68
    {
69
        return count($this->components());
70
    }
71
72
    /**
73
     * Returns the length of the vector.
74
     *
75
     * @api
76
     * @return float The length/magnitude of the vector.
77
     */
78
    public function length()
79
    {
80
        $sumOfSquares = 0;
81
        foreach ($this->components() as $component) {
82
            $sumOfSquares += pow($component, 2);
83
        }
84
85
        return sqrt($sumOfSquares);
86
    }
87
88
    /**
89
     * Check whether the given vector is the same as this vector.
90
     *
91
     * @api
92
     * @param self $b The vector to check for equality.
93
     * @return bool True if the vectors are equal and false otherwise.
94
     */
95
    public function isEqual(self $b)
96
    {
97
        return $this->components() === $b->components();
98
    }
99
100
    /**
101
     * Checks whether the two vectors are of the same dimension.
102
     *
103
     * @api
104
     * @param self $b The vector to check against.
105
     * @return bool True if the vectors are of the same dimension, false otherwise.
106
     */
107
    public function isSameDimension(self $b)
108
    {
109
        return $this->dimension() === $b->dimension();
110
    }
111
112
    /**
113
     * Checks whether the two vectors are of the same vector space.
114
     *
115
     * @api
116
     * @param self $b The vector to check against.
117
     * @return bool True if the vectors are the same vector space, false otherwise.
118
     */
119
    public function isSameVectorSpace(self $b)
120
    {
121
        return array_keys($this->components()) === array_keys($b->components());
122
    }
123
124
    /**
125
     * Adds two vectors together.
126
     *
127
     * @api
128
     * @param self $b The vector to add.
129
     * @return self The sum of the two vectors.
130
     * @throws Exception if the vectors are not in the same vector space.
131
     * @see self::_checkVectorSpace() For exception information.
132
     */
133
    public function add(self $b)
134
    {
135
        $this->_checkVectorSpace($b);
136
137
        $bComponents = $b->components();
138
        $sum = [];
139
        foreach ($this->components() as $i => $component) {
140
            $sum[$i] = $component + $bComponents[$i];
141
        }
142
143
        return new static($sum);
144
    }
145
146
    /**
147
     * Subtracts the given vector from this vector.
148
     *
149
     * @api
150
     * @param self $b The vector to subtract from this vector.
151
     * @return self The difference of the two vectors.
152
     * @throws Exception if the vectors are not in the same vector space.
153
     * @see self::_checkVectorSpace() For exception information.
154
     */
155
    public function subtract(self $b)
156
    {
157
        return $this->add($b->multiplyByScalar(-1));
158
    }
159
160
    /**
161
     * Computes the dot product, or scalar product, of two vectors.
162
     *
163
     * @api
164
     * @param self $b The vector to multiply with.
165
     * @return int|float The dot product of the two vectors.
166
     * @throws Exception if the vectors are not in the same vector space.
167
     * @see self::_checkVectorSpace() For exception information.
168
     */
169
    public function dotProduct(self $b)
170
    {
171
        $this->_checkVectorSpace($b);
172
173
        $bComponents = $b->components();
174
        $product = 0;
175
        foreach ($this->components() as $i => $component) {
176
            $product += $component * $bComponents[$i];
177
        }
178
179
        return $product;
180
    }
181
182
    /**
183
     * Computes the cross product, or vector product, of two vectors.
184
     *
185
     * @api
186
     * @param self $b The vector to multiply with.
187
     * @return self The cross product of the two vectors.
188
     * @throws Exception if the vectors are not 3-dimensional.
189
     * @throws Exception if the vectors are not in the same vector space.
190
     * @see self::_checkVectorSpace() For exception information.
191
     */
192
    public function crossProduct(self $b)
193
    {
194
        $this->_checkVectorSpace($b);
195
        if ($this->dimension() !== 3) {
196
            throw new Exception('Both vectors must be 3-dimensional');
197
        }
198
199
        $tc = $this->components();
200
        $bc = $b->components();
201
        list($k0, $k1, $k2) = array_keys($tc);
202
        $product = [
203
            $k0 => $tc[$k1] * $bc[$k2] - $tc[$k2] * $bc[$k1],
204
            $k1 => $tc[$k2] * $bc[$k0] - $tc[$k0] * $bc[$k2],
205
            $k2 => $tc[$k0] * $bc[$k1] - $tc[$k1] * $bc[$k0],
206
        ];
207
208
        return new static($product);
209
    }
210
211
    /**
212
     * Computes the scalar triple product of three vectors.
213
     *
214
     * @api
215
     * @param self $b The second vector of the triple product.
216
     * @param self $c The third vector of the triple product.
217
     * @return int|float The scalar triple product of the three vectors.
218
     * @throws Exception if the vectors are not 3-dimensional.
219
     * @throws Exception if the vectors are not in the same vector space.
220
     * @see self::_checkVectorSpace() For exception information.
221
     */
222
    public function scalarTripleProduct(self $b, self $c)
223
    {
224
        return $this->dotProduct($b->crossProduct($c));
225
    }
226
227
    /**
228
     * Computes the vector triple product of three vectors.
229
     *
230
     * @api
231
     * @param self $b The second vector of the triple product.
232
     * @param self $c The third vector of the triple product.
233
     * @return self The vector triple product of the three vectors.
234
     * @throws Exception if the vectors are not 3-dimensional.
235
     * @throws Exception if the vectors are not in the same vector space.
236
     * @see self::_checkVectorSpace() For exception information.
237
     */
238
    public function vectorTripleProduct(self $b, self $c)
239
    {
240
        return $this->crossProduct($b->crossProduct($c));
241
    }
242
243
    /**
244
     * Multiplies the vector by the given scalar.
245
     *
246
     * @api
247
     * @param int|float $scalar The real number to multiply by.
248
     * @return self The result of the multiplication.
249
     */
250
    public function multiplyByScalar($scalar)
251
    {
252
        $result = [];
253
        foreach ($this->components() as $i => $component) {
254
            $result[$i] = $component * $scalar;
255
        }
256
257
        return new static($result);
258
    }
259
260
    /**
261
     * Divides the vector by the given scalar.
262
     *
263
     * @api
264
     * @param int|float $scalar The real number to divide by.
265
     * @return self The result of the division.
266
     * @throws Exception if the $scalar is 0.
267
     */
268
    public function divideByScalar($scalar)
269
    {
270
        if ($scalar == 0) {
271
            throw new Exception('Cannot divide by zero');
272
        }
273
274
        return $this->multiplyByScalar(1.0 / $scalar);
275
    }
276
277
    /**
278
     * Return the normalized vector.
279
     *
280
     * The normalized vector (or unit vector) is the vector with the same
281
     * direction as this vector, but with a length/magnitude of 1.
282
     *
283
     * @api
284
     * @return self The normalized vector.
285
     * @throws Exception if the vector length is zero.
286
     */
287
    public function normalize()
288
    {
289
        return $this->divideByScalar($this->length());
290
    }
291
292
    /**
293
     * Project the vector onto another vector.
294
     *
295
     * @api
296
     * @param self $b The vector to project this vector onto.
297
     * @return self The vector projection of this vector onto $b.
298
     * @throws Exception if the vector length of $b is zero.
299
     * @throws Exception if the vectors are not in the same vector space.
300
     * @see self::_checkVectorSpace() For exception information.
301
     */
302
    public function projectOnto(self $b)
303
    {
304
        $bUnit = $b->normalize();
305
        return $bUnit->multiplyByScalar($this->dotProduct($bUnit));
306
    }
307
308
    /**
309
     * Returns the angle between the two vectors.
310
     *
311
     * @api
312
     * @param self $b The vector to compute the angle between.
313
     * @return float The angle between the two vectors in radians.
314
     * @throws Exception if either of the vectors are zero-length.
315
     * @throws Exception if the vectors are not in the same vector space.
316
     * @see self::_checkVectorSpace() For exception information.
317
     */
318
    public function angleBetween(self $b)
319
    {
320
        $denominator = $this->length() * $b->length();
321
        if ($denominator == 0) {
322
            throw new Exception('Cannot divide by zero');
323
        }
324
325
        return acos($this->dotProduct($b) / $denominator);
326
    }
327
328
    /**
329
     * Checks that the vector spaces of the two vectors are the same.
330
     *
331
     * The vectors must be of the same dimension and have the same keys in their
332
     * components.
333
     *
334
     * @internal
335
     * @param self $b The vector to check against.
336
     * @return void
337
     * @throws Exception if the vectors are not of the same dimension.
338
     * @throws Exception if the vectors' components down have the same keys.
339
     */
340
    protected function _checkVectorSpace(self $b)
341
    {
342
        if (!$this->isSameDimension($b)) {
343
            throw new Exception('The vectors must be of the same dimension');
344
        }
345
346
        if (!$this->isSameVectorSpace($b)) {
347
            throw new Exception('The vectors\' components must have the same keys');
348
        }
349
    }
350
}
351