繁体   English   中英

如何将 &mut ndarray::Array 传递给函数并使用它执行逐元素算术?

[英]How can I pass an &mut ndarray::Array to a function and perform element-wise arithmetic with it?

动机

我正在尝试在 Rust 中为学校项目创建我的第一个真正的交易程序(不是必需的......我只是对 Rust 着迷并决定我要冒险)。

该项目是基于一些传感器数据、一些概率、对未来奖励的预测和一些其他东西的机器人决策的简单模拟。 该程序由一个主循环组成,其中在未来的某个时间范围内的每个时间步都进行大量数学运算。 被传送到每个后续时间步的数据由矩阵Y表示,该矩阵由一组线性约束的两列线性系数(在每个时间步修改)组成(其中更多的约束/系数行被添加到每个时间步的集合)。

由于该程序将需要大量元素矩阵运算,而且我在 NumPy 方面经验丰富,因此ndarray crate 似乎非常适合这项工作。 我对该程序的思考过程是为Y制作一个可变的 2D 数组,每次循环迭代都会对其进行修改,而不是每次都分配一个新数组。 从那以后,我意识到每次迭代的行数也会增加一个未知的数量,所以也许这种方法不是最好的主意,但我对我得到的错误的问题无论如何都成立。

我的问题是:如果我想通过将数组的引用传递给将修改其数据的多个函数来在循环的每次迭代中修改数组,我如何在基本的逐元素算术运算中也使用相同的数组?

这是我的代码的一个简单示例,用于演示:

extern crate ndarray;

use ndarray::prelude::*;

fn main() {
    let pz = array![[0.7, 0.3], [0.3, 0.7]]; // measurement probabilities

    let mut Y = Array2::<f64>::zeros((1, 2));

    for i in 1..10 {
        do_some_maths(&mut Y, pz);
        // other functions that will modify Y
    }
    
    println!("Result: {}", Y);
}

fn do_some_maths(Y: &mut Array2<f64>, pz: Array2<f64>) {

    let Yp = Y * pz.slice(s![.., 0]);  // <-- this is the problem

    // do lots of matrix math with Yp
    // ...
    // then modify Y's data using Yp (hence Y needs to be &mut)
}

这给出了以下编译错误:

error[E0369]: binary operation `*` cannot be applied to type `&mut ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray::Dim<[usize; 2]>>`
  --> src/main2.rs:21:16
   |
21 |     let Yp = Y * pz.slice(s![.., 0]);  // <-- this is the problem
   |              - ^ ------------------- ndarray::ArrayBase<ndarray::ViewRepr<&f64>, _>
   |              |
   |              &mut ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray::Dim<[usize; 2]>>
   |
   = note: an implementation of `std::ops::Mul` might be missing for `&mut ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray::Dim<[usize; 2]>>`

我花了很多时间试图理解

  1. 我的用例的正确方法是什么,以及
  2. 为什么我写的代码不起作用。

我在这个站点上阅读了几个有些相关的问题,但没有一个真正涉及将 Array 引用作为函数参数处理并对其执行二元运算的情况。

我已经努力学习了 Rust 书的前 5 章并深入研究了ndarray的文档, ndarray我仍然找不到答案。 ndarray的的文档ArrayBase包含以下的解释,我不完全理解:

两个数组的二元运算

让 A 成为任何类型的数组或视图。 设 B 是一个拥有自有存储的数组(Array 或 ArcArray)。 设 C 是一个包含可变数据的数组(Array、ArcArray 或 ArrayViewMut)。 由@ 表示的任意二元运算符支持以下操作数组合(它可以是 +、-、*、/ 等)。

  • &A @ &A 产生一个新的数组
  • B @ A 消耗 B,用结果更新它,并返回它
  • B @&A 消耗 B,用结果更新它,并返回它
  • C @= &A 就地执行算术运算

鉴于此描述,并搜索AddMul等的许多特征实现,在我看来,可变ndarray::Array不能是二元运算中的操作数,除非是复合赋值。

这是真的,还是我在这里遗漏了什么? 我不想简单地记住这个小花絮并继续前进; 我真的很想了解这里到底发生了什么,以及我缺乏理解的地方。 请帮我把我受过 C++/Python 训练的大脑围绕着这个。 :)

您已经回答了自己的问题:您尝试执行的乘法是&C @ B ,它不是ndarray支持的四个ndarray 此外,您将pz作为值传递给函数。 它在循环的第一轮被消耗,其余部分不再可用。 所以这也不会编译。

这有效:

extern crate ndarray;
use ndarray::prelude::*;

fn main() {
    let pz = array![[0.7, 0.3], [0.3, 0.7]];
    let mut y = Array2::<f64>::zeros((1, 2));

    for _ in 1..10 {
        do_some_maths(&mut y, &pz);
    }

    println!("Result: {}", y);
}

fn do_some_maths(y: &mut Array2<f64>, pz: &Array2<f64>) {
    *y *= &pz.slice(s![.., 0]);
}

可变引用比不可变引用“更强大”,并且您始终可以将可变引用用作不可变引用,因此这不是问题。

正如 edwardw 指出的那样,您可能不想在每个循环中都使用数组pz (而且编译器无论如何都不会让您这样做)。 事实上,如果你考虑一下你的do_some_maths函数的签名,你所拥有的是:

  • 要修改的可变数组
  • 您另外使用的不可变的

所以签名是:

fn do_some_maths(y: &mut Array2<f64>, pz: &Array2<f64>) {
   ...
}

现在, ndarray crate 允许您:

  • 就地修改值或
  • 为您的运营创建新的

一般来说,它的输入是非常明智的,只要有可能就引用,以免消耗你的输入数组。 这意味着可能需要大量(取消)引用,您可以随意使用。 在 numpy 中,几乎所有的东西都是一个引用,所以你不必担心,但逻辑是一样的。

如果你想从Y创建Yp ,你可以通过分配一个新的Yp值来实现:

fn do_some_maths(y: &mut Array2<f64>, pz: &Array2<f64>) {
    // yp is a new Array2<f64>
    let yp: Array2<f64> = y * pz;
    // We may want to modify `y` now
    y.scaled_add(-2.3, yp);
    y *= pz;
}

这里进行的各种操作是:

  • &Array2 * &Array2 -> Array2
  • scaled_add(self: &mut Array2, f64, &Array2) -> (),就地修改数组
  • 就地标量运算 &mut Array2 *= &Array2

一般来说,尽量使用引用(可变或不可变),除非您知道应该使用输入。

澄清与 numpy 的相似之处:numpy 数组本质上都是引用。 Rust 为您提供了直接传递值(因此被消耗 - 将它们视为使用一次,然后它们被销毁)或引用(可变或不可变,取决于您是否需要改变它们)的粒度。 Numpy 在任何地方都使用本质上可变的引用(除非您显式切换WRITEABLE标志)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM