Skip to content

Commit c0f8530

Browse files
authored
Merge pull request dimforge#1055 from dimforge/fix-pow
Fix Matrix::pow and make it work with integer matrices
2 parents 99ac8c4 + d806669 commit c0f8530

File tree

3 files changed

+89
-51
lines changed

3 files changed

+89
-51
lines changed

src/linalg/pow.rs

Lines changed: 39 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,71 @@
11
//! This module provides the matrix exponential (pow) function to square matrices.
22
3-
use std::ops::DivAssign;
4-
53
use crate::{
64
allocator::Allocator,
75
storage::{Storage, StorageMut},
8-
DefaultAllocator, DimMin, Matrix, OMatrix,
6+
DefaultAllocator, DimMin, Matrix, OMatrix, Scalar,
97
};
10-
use num::PrimInt;
11-
use simba::scalar::ComplexField;
8+
use num::{One, Zero};
9+
use simba::scalar::{ClosedAdd, ClosedMul};
1210

13-
impl<T: ComplexField, D, S> Matrix<T, D, D, S>
11+
impl<T, D, S> Matrix<T, D, D, S>
1412
where
13+
T: Scalar + Zero + One + ClosedAdd + ClosedMul,
1514
D: DimMin<D, Output = D>,
1615
S: StorageMut<T, D, D>,
1716
DefaultAllocator: Allocator<T, D, D> + Allocator<T, D>,
1817
{
19-
/// Attempts to raise this matrix to an integral power `e` in-place. If this
20-
/// matrix is non-invertible and `e` is negative, it leaves this matrix
21-
/// untouched and returns `false`. Otherwise, it returns `true` and
22-
/// overwrites this matrix with the result.
23-
pub fn pow_mut<I: PrimInt + DivAssign>(&mut self, mut e: I) -> bool {
24-
let zero = I::zero();
25-
18+
/// Raises this matrix to an integral power `exp` in-place.
19+
pub fn pow_mut(&mut self, mut exp: u32) {
2620
// A matrix raised to the zeroth power is just the identity.
27-
if e == zero {
21+
if exp == 0 {
2822
self.fill_with_identity();
29-
return true;
30-
}
31-
32-
// If e is negative, we compute the inverse matrix, then raise it to the
33-
// power of -e.
34-
if e < zero && !self.try_inverse_mut() {
35-
return false;
36-
}
23+
} else if exp > 1 {
24+
// We use the buffer to hold the result of multiplier^2, thus avoiding
25+
// extra allocations.
26+
let mut x = self.clone_owned();
27+
let mut workspace = self.clone_owned();
3728

38-
let one = I::one();
39-
let two = I::from(2u8).unwrap();
29+
if exp % 2 == 0 {
30+
self.fill_with_identity();
31+
} else {
32+
// Avoid an useless multiplication by the identity
33+
// if the exponent is odd.
34+
exp -= 1;
35+
}
4036

41-
// We use the buffer to hold the result of multiplier ^ 2, thus avoiding
42-
// extra allocations.
43-
let mut multiplier = self.clone_owned();
44-
let mut buf = self.clone_owned();
37+
// Exponentiation by squares.
38+
loop {
39+
if exp % 2 == 1 {
40+
self.mul_to(&x, &mut workspace);
41+
self.copy_from(&workspace);
42+
}
4543

46-
// Exponentiation by squares.
47-
loop {
48-
if e % two == one {
49-
self.mul_to(&multiplier, &mut buf);
50-
self.copy_from(&buf);
51-
}
44+
exp /= 2;
5245

53-
e /= two;
54-
multiplier.mul_to(&multiplier, &mut buf);
55-
multiplier.copy_from(&buf);
46+
if exp == 0 {
47+
break;
48+
}
5649

57-
if e == zero {
58-
return true;
50+
x.mul_to(&x, &mut workspace);
51+
x.copy_from(&workspace);
5952
}
6053
}
6154
}
6255
}
6356

64-
impl<T: ComplexField, D, S: Storage<T, D, D>> Matrix<T, D, D, S>
57+
impl<T, D, S: Storage<T, D, D>> Matrix<T, D, D, S>
6558
where
59+
T: Scalar + Zero + One + ClosedAdd + ClosedMul,
6660
D: DimMin<D, Output = D>,
6761
S: StorageMut<T, D, D>,
6862
DefaultAllocator: Allocator<T, D, D> + Allocator<T, D>,
6963
{
70-
/// Attempts to raise this matrix to an integral power `e`. If this matrix
71-
/// is non-invertible and `e` is negative, it returns `None`. Otherwise, it
72-
/// returns the result as a new matrix. Uses exponentiation by squares.
64+
/// Raise this matrix to an integral power `exp`.
7365
#[must_use]
74-
pub fn pow<I: PrimInt + DivAssign>(&self, e: I) -> Option<OMatrix<T, D, D>> {
75-
let mut clone = self.clone_owned();
76-
77-
if clone.pow_mut(e) {
78-
Some(clone)
79-
} else {
80-
None
81-
}
66+
pub fn pow(&self, exp: u32) -> OMatrix<T, D, D> {
67+
let mut result = self.clone_owned();
68+
result.pow_mut(exp);
69+
result
8270
}
8371
}

tests/linalg/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod full_piv_lu;
99
mod hessenberg;
1010
mod inverse;
1111
mod lu;
12+
mod pow;
1213
mod qr;
1314
mod schur;
1415
mod solve;

tests/linalg/pow.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#[cfg(feature = "proptest-support")]
2+
mod proptest_tests {
3+
macro_rules! gen_tests(
4+
($module: ident, $scalar: expr, $scalar_type: ty) => {
5+
mod $module {
6+
use na::DMatrix;
7+
#[allow(unused_imports)]
8+
use crate::core::helper::{RandScalar, RandComplex};
9+
use std::cmp;
10+
11+
use crate::proptest::*;
12+
use proptest::{prop_assert, proptest};
13+
14+
proptest! {
15+
#[test]
16+
fn pow(n in PROPTEST_MATRIX_DIM, p in 0u32..=4) {
17+
let n = cmp::max(1, cmp::min(n, 10));
18+
let m = DMatrix::<$scalar_type>::new_random(n, n).map(|e| e.0);
19+
let m_pow = m.pow(p);
20+
let mut expected = m.clone();
21+
expected.fill_with_identity();
22+
23+
for _ in 0..p {
24+
expected = &m * &expected;
25+
}
26+
27+
prop_assert!(relative_eq!(m_pow, expected, epsilon = 1.0e-5))
28+
}
29+
30+
#[test]
31+
fn pow_static_square_4x4(m in matrix4_($scalar), p in 0u32..=4) {
32+
let mut expected = m.clone();
33+
let m_pow = m.pow(p);
34+
expected.fill_with_identity();
35+
36+
for _ in 0..p {
37+
expected = &m * &expected;
38+
}
39+
40+
prop_assert!(relative_eq!(m_pow, expected, epsilon = 1.0e-5))
41+
}
42+
}
43+
}
44+
}
45+
);
46+
47+
gen_tests!(complex, complex_f64(), RandComplex<f64>);
48+
gen_tests!(f64, PROPTEST_F64, RandScalar<f64>);
49+
}

0 commit comments

Comments
 (0)