const arrays in rust - initialise with list comprehension? - rust

Is there a way to use closures or list comprehension when defining constant arrays in Rust? Suppose we have these three const arrays:
const K1 : [i32;8] = [100, 100, 101, 102, 104, 106, 108, 900];
const K2 : [i32;8] = [-100, -100, -101, -102, -104, -106, -108, -900];
const K3 : [i32;8] = [-900, -108, -106, -104, -102, -101, -100, -100];
In python K2 and K3 could be expressed as
K2=[-x for x in K1]
K3=[-x for x in K1[::-1]
Rust has the cute crate which emulates python list comprehension, but I don't think it can be used to define constants. So is there a more elegant solution than simply typing out K2 and K3 as above?

Someday, when Rust has more constant-evaluation abilities, we may be able to write the following:
const K1: [i32; 8] = [100, 100, 101, 102, 104, 106, 108, 900];
const K2: [i32; 8] = K1.map(|x| -x);
const K3: [i32; 8] = { let mut a = K2; a.reverse(); a };
However, for now, neither array::map() nor slice::reverse() are const fns, so you can't use them in constants. map in particular is going to be more trouble because it requires the ability to define higher-order const fns which is not yet available.
However, we can define our own const functions to do the specific jobs we need. This requires a lot more code, so it's not a great idea if you have only one case, but it could be worthwhile anyway to help readability of the constant definitions, or if these situations come up more than once.
const K1: [i32; 8] = [100, 100, 101, 102, 104, 106, 108, 900];
const K2: [i32; 8] = array_i32_mul(-1, K1);
const K3: [i32; 8] = array_reverse(K2);
const fn array_i32_mul<const N: usize>(factor: i32, mut a: [i32; N]) -> [i32; N] {
let mut i = 0;
while i < N {
a[i] *= factor;
i += 1;
}
a
}
const fn array_reverse<T: Copy, const N: usize>(mut a: [T; N]) -> [T; N] {
let mut i = 0;
while i < N / 2 {
let from_end = N - i - 1;
(a[i], a[from_end]) = (a[from_end], a[i]);
i += 1;
}
a
}
fn main() {
dbg!(K1, K2, K3);
}
Notes:
It's not possible to use for loops in const fns yet, because traits aren't supported and for uses Iterator which is a trait, so we have to use while for all our indexing.
array_reverse would not need T: Copy if std::mem::swap() were const, but it isn't yet.
The advantages of going to this effort over using Lazy, as suggested in another answer, are:
The constants can be used in situations where the numeric values are actually required to be constants — with Lazy accessing the value is only possible at run time.
Accessing the values doesn't require a run-time check for whether they've been initialized yet.
The type is an array type, rather than something that merely derefs to an array.

I would try with once_cell
I don't find this as elegant as list-comprehensions, but maybe it's enough for the intended usage.
use once_cell::sync::Lazy;
const K1: [i32; 8] = [100, 100, 101, 102, 104, 106, 108, 900];
const K2: Lazy<[i32; K1.len()]> = Lazy::new(|| {
let mut k2 = [0; K1.len()];
k2.iter_mut().zip(K1.iter()).for_each(|(x2, x1)| *x2 = -*x1);
k2
});
const K3: Lazy<[i32; K1.len()]> = Lazy::new(|| {
let mut k3 = [0; K1.len()];
k3.iter_mut().zip(K1.iter().rev()).for_each(|(x3, x1)| *x3 = -*x1);
k3
});
fn main() {
println!("{:?}", K1);
println!("{:?}", *K2);
println!("{:?}", *K3);
}
/*
[100, 100, 101, 102, 104, 106, 108, 900]
[-100, -100, -101, -102, -104, -106, -108, -900]
[-900, -108, -106, -104, -102, -101, -100, -100]
*/

Related

Why does a borrowed variable not change when the borrowed from variable changes?

I'm new to rust and trying to understand the borrowing principle.
I have the following code:
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let largest_nbr = &number_list[0];
println!("The largest number is {}", largest_nbr);
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
println!("The largest number is {}", largest_nbr);
}
When I execute cargo run I get this result:
> The largest number is 34
> The largest number is 34
I expected the second line to say 102 is the largest number because largest_nbr borrows from number_list, so the pointer is showing at the storage of number_list. When the value of number_list changes, shouldn't the value of largest_nbr also change?
The line:
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
does not modify the original number_list variable. It creates a new one and names it identically, shadowing the original name.
In simpler terms that means that now two number_list variables exist, but the first one is no longer accessible because the second one has taken over the name. The largest_nbr variable, however, references the first one.
Note that what you are trying here is impossible, because you cannot modify the array while you borrow parts of it. I guess this is why you added a let to it, because if you would simply write number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8], without the let, you would get an error that tells you that you can't modify it while it is borrowed in largest_nbr.
See:
fn main() {
let mut number_list = vec![34, 50, 25, 100, 65];
let largest_nbr = &number_list[0];
println!("The largest number is {}", largest_nbr);
number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
println!("The largest number is {}", largest_nbr);
}
error[E0506]: cannot assign to `number_list` because it is borrowed
--> src/main.rs:8:5
|
4 | let largest_nbr = &number_list[0];
| ----------- borrow of `number_list` occurs here
...
8 | number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
| ^^^^^^^^^^^ assignment to borrowed `number_list` occurs here
9 |
10 | println!("The largest number is {}", largest_nbr);
| ----------- borrow later used here
You’re not changing value of number_list, you’re shadowing it. And even if you would’ve changed your number_list, largest_number wouldn’t have changed (just dropped). Why? Because a Vec is basically just a pointer, length and capacity and is not connected to a buffer. Only arrays (not slices!) are real buffers. So how do you change your Vec? Nohow! You can’t mutate a variable while it’s still borrowed as immutable. And you don’t need this, so don’t try.

Clean way to collect into matrix

I'd like to split and collect this strangely-shaped vector
let v = vec![
0, 1, 2, 3,
4, 5, 6, 7,
8, 9,
10, 11,
12, 13,
];
into these two matrices:
let v1 = vec![
vec![0, 1, 2, 3],
vec![4, 5, 6, 7],
];
let v2 = vec![
vec![8, 9],
vec![10, 11],
vec![12, 13],
];
(The elements are sequential (i.e. 1, 2, 3, ...) but this is just for example. Also, though the number of matrices are two here, this number is just for example; sometimes it should be three or more.)
Trivially it is possible (Rust Playground):
let mut v1: Vec<Vec<usize>> = vec![];
for i in 0..2 {
v1.push(v.iter().skip(i * 4).take(4).copied().collect());
}
let mut v2: Vec<Vec<usize>> = vec![];
for i in 0..3 {
v2.push(v.iter().skip(8 + i * 2).take(2).copied().collect());
}
But, is there a cleaner way? Here's the pseudo code I want:
let v1 = v.iter().every(4).take(2).collect();
let v2 = v.iter().skip(8).every(2).take(3).collect();
You can split the initial vector into two slices and iterate each of them separately (playground):
let (left, right) = v.split_at(8);
let v1 = left.chunks(4).map(|s| s.to_vec()).collect::<Vec<_>>();
let v2 = right.chunks(2).map(|s| s.to_vec()).collect::<Vec<_>>();
If an external crate is allowed, you can use Itertools::chunks:
v.iter().chunks(4).into_iter().take(2).map(|l| l.copied().collect_vec()).collect()
(Rust Playground)

efficient SIMD dot product in rust

I'm trying to create efficient SIMD version of dot product to implement 2D convolution for i16 type for FIR filter.
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[target_feature(enable = "avx2")]
unsafe fn dot_product(a: &[i16], b: &[i16]) {
let a = a.as_ptr() as *const [i16; 16];
let b = b.as_ptr() as *const [i16; 16];
let a = std::mem::transmute(*a);
let b = std::mem::transmute(*b);
let ms_256 = _mm256_mullo_epi16(a, b);
dbg!(std::mem::transmute::<_, [i16; 16]>(ms_256));
let hi_128 = _mm256_castsi256_si128(ms_256);
let lo_128 = _mm256_extracti128_si256(ms_256, 1);
dbg!(std::mem::transmute::<_, [i16; 8]>(hi_128));
dbg!(std::mem::transmute::<_, [i16; 8]>(lo_128));
let temp = _mm_add_epi16(hi_128, lo_128);
}
fn main() {
let a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
unsafe {
dot_product(&a, &b);
}
}
I] ~/c/simd (master|…) $ env RUSTFLAGS="-C target-cpu=native" cargo run --release | wl-copy
warning: unused variable: `temp`
--> src/main.rs:16:9
|
16 | let temp = _mm_add_epi16(hi_128, lo_128);
| ^^^^ help: if this is intentional, prefix it with an underscore: `_temp`
|
= note: `#[warn(unused_variables)]` on by default
warning: 1 warning emitted
Finished release [optimized] target(s) in 0.00s
Running `target/release/simd`
[src/main.rs:11] std::mem::transmute::<_, [i16; 16]>(ms_256) = [
0,
1,
4,
9,
16,
25,
36,
49,
64,
81,
100,
121,
144,
169,
196,
225,
]
[src/main.rs:14] std::mem::transmute::<_, [i16; 8]>(hi_128) = [
0,
1,
4,
9,
16,
25,
36,
49,
]
[src/main.rs:15] std::mem::transmute::<_, [i16; 8]>(lo_128) = [
64,
81,
100,
121,
144,
169,
196,
225,
]
While I understand SIMD conceptually I'm not familiar with exact instructions and intrinsics.
I know what I need to multiply two vectors and then horizontally sum then by halving them and using instructions to vertically add two halved of lower size.
I've found madd instruction which supposedly should do one such summation after multiplications right away, but not sure what to do with the result.
If using mul instead of madd I'm not sure which instructions to use to reduce the result further.
Any help is welcome!
PS
I've tried packed_simd but it seems like it doesn't work on stable rust.

How to concatenate the individual elements of a vector of slices with other slices?

I have a slice of bytes start = [30u8; 5] and middle = [40u8; 3] and a vector of byte slices:
let first = [1u8; 10];
let second = [2u8; 10];
let third = [3u8; 10];
let elements: Vec<[u8; 10]> = vec![first, second, third];
I want to concatenate everything together, in such a way that I will obtain a single byte slice which looks as
[30, 30, 30, 30, 30, 40, 40, 40, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
However, although I can concatenate start and middle when I try to append the vector elements it fails. I know that I am wrongly trying to iterate through the elements of the vector to concatenate, but I can't figure out how to do it correctly?
fn main() {
let start = [30u8; 5];
let middle = [40u8; 4];
let first = [1u8; 10];
let second = [2u8; 10];
let third = [3u8; 10];
let elements: Vec<[u8; 10]> = vec![first, second, third];
println!("{:?}", elements.iter());
for key in elements.iter() {
println!("{:?}", key.iter());
}
let alltogether: Vec<u8> = start
.iter()
.cloned()
.chain(middle.iter().cloned())
.chain(elements.iter().iter().cloned())
.collect();
println!("{:?}", alltogether);
}
This example can be copy-pasted into the Rust playground.
You possibly want this:
let alltogether: Vec<u8> = start
.iter()
.cloned()
.chain(middle.iter().cloned())
.chain(elements.iter().flatten().cloned())
.collect();
Note that there is also copied (instead of cloned) that can be used for Copyable types.
If the stuff in elements does not implement IntoIterator itself, you can use flat_map to specify how to convert one element to an iterator.

Checking equality of two TriMat matrices written to Matrix Market format in sprs does not work if insertion order is different

I am trying to check if two Matrix Market format files contain the same matrix. In my actual code, due to the use of multi-threading, there is no guarantee that I am inserting items into a TriMat in the same order before being serialized to disk. As a result, when I load the resulting files and compare them, they are not always the same. How can I compare two different .mtx files and ensure that they are the same, regardless of insertion order?
Example code:
extern crate sprs;
use sprs::io::{write_matrix_market, read_matrix_market};
use sprs::TriMat;
fn main() {
let mut mat = TriMat::new((4, 20));
let mut vals = Vec::new();
vals.push((0, 19, 1));
vals.push((1, 14, 1));
vals.push((1, 19, 1));
vals.push((2, 17, 2));
for (i, j, v) in vals {
mat.add_triplet(i, j, v)
}
let _ = write_matrix_market("a.mtx", &mat).unwrap();
let mut mat2 = TriMat::new((4, 20));
let mut vals2 = Vec::new();
vals2.push((0, 19, 1));
vals2.push((1, 14, 1));
vals2.push((2, 17, 2)); // different order
vals2.push((1, 19, 1));
for (i, j, v) in vals2 {
mat2.add_triplet(i, j, v)
}
let _ = write_matrix_market("b.mtx", &mat2).unwrap();
let seen_mat: TriMat<usize> = read_matrix_market("a.mtx").unwrap();
let expected_mat: TriMat<usize> = read_matrix_market("b.mtx").unwrap();
assert_eq!(seen_mat, expected_mat);
}
And the resulting error:
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `TriMatBase { rows: 4, cols: 20, row_inds: [0, 1, 1, 2], col_inds: [19, 14, 19, 17], data: [1, 1, 1, 2] }`,
right: `TriMatBase { rows: 4, cols: 20, row_inds: [0, 1, 2, 1], col_inds: [19, 14, 17, 19], data: [1, 1, 2, 1] }`', src/main.rs:31:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.
You can see that these two matrices are actually identical, but that the items have been inserted in different orders.
Turns out you can convert to CSR to get it to work:
let seen_mat: TriMat<usize> = read_matrix_market("a.mtx").unwrap();
let expected_mat: TriMat<usize> = read_matrix_market("b.mtx").unwrap();
let a = seen_mat.to_csr();
let b = expected_mat.to_csr();
assert_eq!(a, b);

Resources