3

I'm trying to create a na::Matrix2x3 by copying from the first two rows of a na::Matrix3. The result will be stored in a struct.

I don't know how to do this and the documentation is confusing. I tried clone(), into(), from(...), etc., but nothing seems to work.

Here is a reproducible example where I have tried to use .clone() (playground):

extern crate nalgebra as na; // 0.27.1

fn main() {
    let proj33 = na::Matrix3::<f64>::zeros();
    let proj: na::Matrix2x3<f64> = proj33.rows(0, 2).clone();
}
error[E0308]: mismatched types
 --> src/main.rs:5:36
  |
5 |     let proj: na::Matrix2x3<f64> = proj33.rows(0, 2).clone();
  |               ------------------   ^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Const`, found struct `Dynamic`
  |               |
  |               expected due to this
  |
  = note: expected struct `Matrix<_, Const<2_usize>, _, ArrayStorage<f64, 2_usize, 3_usize>>`
             found struct `Matrix<_, Dynamic, _, SliceStorage<'_, f64, Dynamic, Const<3_usize>, Const<1_usize>, Const<3_usize>>>`

This seems to work, but it is wasteful as it initializes the matrix to zeros first:

let mut proj = na::Matrix2x3::<f64>::zeros();
proj.copy_from(&proj33.rows(0, 2));

In Eigen (C++) I would have done this:

const Eigen::Matrix<double, 2, 3> proj = proj33.topRows(2);
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Daniel
  • 510
  • 3
  • 15
  • *it is wasteful* — are you sure it's wasteful and that it hasn't been optimized out? – Shepmaster Jul 12 '21 at 19:54
  • Even if it has been optimized out, it is verbose and dangerous to have an unnecessary `mut` if it turns out that in fact `nalgebra` does have some constructor method that can accomplish this in one line. – Daniel Jul 12 '21 at 20:02
  • *dangerous* seems like a bit of an overreaction. See also [What does 'let x = x' do in Rust?](https://stackoverflow.com/q/54595345/155423) – Shepmaster Jul 12 '21 at 20:03
  • I meant dangerous as in the additional complexity makes it easier for programmers to make mistakes when working on that piece of code. Imagine if Rust didn't let you assign integers, and instead of `let a = 123;` you had to do `let mut a = 0; a.copy(123); let a = a;`. And then, when asking about whether there is a better way of doing that, people say, "ah but it's safe and probably has been optimized by the compiler". Sure, but the latter method is much more difficult to understand and results in a greater likelihood of bugs if you had to do it every single time. – Daniel Jul 12 '21 at 20:18

3 Answers3

3

Okay, I have figured it out. I have to use fixed_slice instead of (or in addition to) just rows --- since the size of the output (Matrix2x3) is known to the compiler, the slice must be as well.

The key is to use:

.fixed_slice::<2, 3>(0, 0).into()

This works for dynamic matrices (playground):

extern crate nalgebra as na;

fn main() {
    let proj33 = na::DMatrix::identity(3, 3);
    let proj: na::Matrix2x3<f64> = proj33.fixed_slice::<2, 3>(0, 0).into();
}

and dynamic matrix views (aka matrices with SliceStorage instead of ArrayStorage) too (playground):

extern crate nalgebra as na;

fn main() {
    let proj33 = na::DMatrix::identity(3, 3);
    let proj33_view = proj33.rows(0, 2);
    dbg!(&proj33_view);
    let proj: na::Matrix2x3<f64> = proj33_view.fixed_slice::<2, 3>(0, 0).into();
}

However, when slicing a dynamic matrix, bear in mind that it will panic if you slice out of bounds.

Daniel
  • 510
  • 3
  • 15
2

…or probably better suited: fixed_rows()

let proj33 = na::Matrix2x3::<f64>::zeros();
let proj: na::Matrix2x3<f64> = proj33.fixed_rows::<2>(0).into();
Kaplan
  • 2,572
  • 13
  • 14
1

Now, I kinda agree with @Shepmaster that this probably really is completely unnecessary, but if you absolutely cannot stand initializing the Matrix with zeroes, you can use a bit of unsafe:

extern crate nalgebra as na;

fn main() {
    let proj33 = na::Matrix3::<f64>::zeros();
    let proj = unsafe {
        // create a new matrix with probably a bunch of garbage in it
        let mut p = na::Matrix2x3::<f64>::new_uninitialized();
        // copy the data from the other matrix into the unitialized one
        (*p.as_mut_ptr()).copy_from(&proj33.rows(0, 2));
        // pinky promise that it's initialized now
        p.assume_init()
    };
    
    println!("{}", proj);
}

Disclaimer: Matrix::new_unitialized isn't super well documented, so I don't know if this is actually safe or if there are some invariants I'm unaware of. I don't see why this shouldn't be okay, but those are of course famous last words.

isaactfa
  • 5,461
  • 1
  • 10
  • 24
  • I'm highly surprised that `nalgebra` doesn't have a `Matrix::new_from_copy` or some kind of initializer that simply copies things from a matrix. I guess I'll just stick to using `::zero` then... I agree with you that the `new_uninitialized` method is too hacky and potentially unsafe. – Daniel Jul 12 '21 at 21:27