改变嵌套循环内的项目

Geo*_*ger 6 nested-loops rust

我正在尝试编写一个程序,以均匀(或尽可能接近)分布关于球体表面的点.我试图通过在单位球体上随机放置N个点,然后运行多个步骤,其中点相互排斥来实现此目的.

问题出在点数组的循环中.下面的代码在每个点上循环,然后在其中循环,再次遍历每个点并计算每个点对之间的排斥力.

for point in points.iter_mut() {
    point.movement = Quaternion::identity();
    for neighbour in &points {
        if neighbour.id == point.id {
            continue;
        }
        let angle = point.pos.angle(&neighbour.pos);
        let axis = point.pos.cross(&neighbour.pos);
        let force = -(1.0/(angle*angle)) * update_amt;
        point.movement = point.movement * Quaternion::angle_axis(angle, axis);
    }
}
Run Code Online (Sandbox Code Playgroud)

我收到错误:

src/main.rs:71:27: 71:33 error: cannot borrow `points` as immutable because it is also borrowed as mutable
src/main.rs:71         for neighbour in &points {
Run Code Online (Sandbox Code Playgroud)

和解释

the mutable borrow prevents subsequent moves, borrows, or modification of `points` until the borrow ends
Run Code Online (Sandbox Code Playgroud)

我是Rust的新手,来自C++背景,我不知道如何在Rust中使这种模式工作.

任何解决方案都将非常感激,因为我现在绝对坚持想法.

huo*_*uon 8

有几种方法可以写这个.

一个是你的建议,即将运动分成单独的向量,因为这确保了对运动值的可变借用不会强制编译器保守地禁止访问其余部分points以避免可变借用别名.在Rust中,&mut引用永远不会别名:如果值只能通过一个路径访问,则可以保证任何/所有突变都是内存安全的,如果存在别名则需要花费更多精力(包括运行时检查)以确保突变是安全的.

另一种是使用外环的标记:

for i in 0..points.len() {
    let mut movement = Quaternion::identity();
    for neighbour in &points {
        if neighbour.id == points[i].id {
            continue
        }
        // ...
        movement = movement * Quaternion::angle_axis(angle, axis);
    }

    points[i].movement = movement
}
Run Code Online (Sandbox Code Playgroud)

第三种方法是改变循环的工作方式:当考虑点与其邻居之间的交互时,同时更新点和邻居.这允许人们"三角形"迭代:点0 与之交互1..n,点12..n...... 交互(其中n = points.len()).这可以通过编译器理解不会别名的方式完成.

首先必须将所有movements 重置为1.然后主循环由一个选择单个元素的外部循环组成,然后可以"重新借用"内部循环的迭代器.外部循环不能使用for,因为for将取得迭代器的所有权,幸运的是,while let允许人们写得很整齐:

for point in &mut points {
    point.movement = Quaternion::identity()
}
let mut iter = points.iter_mut();
while let Some(point) = iter.next() {
    // this reborrows the iterator by converting back to a slice
    // (with a shorter lifetime) which is coerced to an iterator 
    // by `for`. This then iterates over the elements after `point`
    for neighbour in &mut iter[..] {
        // `neighbour` is automatically distinct from `point`

        let angle = point.pos.angle(&neighbour.pos);
        let axis = point.pos.cross(&neighbour.pos);
        let force = -(1.0 / (angle*angle)) * update_amt;
        point.movement = point.movement * Quaternion::angle_axis(angle, axis);
        neighbour.movement = neighbour.movement * Quaternion::angle_axis(angle, -axis);
    }
}
Run Code Online (Sandbox Code Playgroud)

NB.我认为axis必须撤消neighbour更新.(我没有编译过,但希望它很接近.)

从理论上讲,后者与目前为止的任何其他建议相比,提供了大约2倍的加速.至少,它减少了计算次数angle,axisforcen * (n - 1)n * (n - 1) / 2.


Geo*_*ger 1

我找到了我自己问题的答案

for (point, movement) in points.iter().zip(movements.iter_mut()) {
    *movement = Quaternion::identity();
    for neighbour in &points {
        if neighbour.id == point.id {
            continue;
        }
        let angle = point.pos.angle(&neighbour.pos);
        let axis = point.pos.cross(&neighbour.pos);
        let force = -(1.0/(angle*angle)) * update_amt;
        *movement = (*movement) * Quaternion::angle_axis(angle, axis);
    }
}
Run Code Online (Sandbox Code Playgroud)

通过创建一个单独的向量来保存运动,而不是让运动成为点的特征,我可以借用点向量两次作为不可变向量,并借用运动向量一次作为可变向量。

我认为 C++ 思维方式仍然让我在类方面思考太多,但我可能仍然错误地认为这是在 Rust 中处理这种模式的理想方法。如果有人对此解决方案有疑问,或者只是知道更好的方法来解决它,任何建议都会很棒。