Mutation of an element inside nested loops

I am trying to write a program to evenly (or as close as possible) distribute points around the surface of a sphere. I try to achieve this by accidentally placing N points around a unit sphere, and then follow a few steps where the points repel each other.

The problem is the loop over the array of points. The code below crosses each point, and then the loop inside it again iterates over each point and calculates the repulsive force between each pair of points.

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); } } 

I get an error message:

 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 { 

and explanation

 the mutable borrow prevents subsequent moves, borrows, or modification of `points` until the borrow ends 

I am new to Rust, coming from a background in C ++, and I have no idea how to make this work template in Rust.

Any solutions for this will be highly appreciated, as I am now absolutely fixated on ideas. Thanks.

Edit:

I developed a solution, which I detailed in the answer below. However, I do not know if this is suitable for solving the problem, so I am still open to suggestions.

+6
source share
2 answers

There are several ways to write this.

One of your suggestions is to separate the motions into a separate vector, as this ensures that mutable borrowing of the motion value does not force the compiler to conservatively deny access to the rest of the points to avoid mutable borrowing from anti-aliasing. In Rust, the &mut link can never be an alias: if the values available in only one way, it is guaranteed that any / all mutations will be safe in memory, if there is smoothing, more effort is required (including runtime checks) to rantirovat that mutations are safe.

Another is to use directions for the outer loop:

 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 } 

The third method is to change how the loops work: when considering the interaction between a point and its neighbor, update the point and the neighboring one at the same time. This allows the iteration to be โ€œtriangularโ€: point 0 interacts with 1..n , point 1 interacts with 2..n , ... (where n = points.len() ). This can be done so that the compiler understands that this is not an alias.

First you need to reset all movement to 1 . The main loops then consist of an outer loop that selects one element, and then you can "flip" the iterator for the inner loop. The outer loop cannot use for , since for will own an iterator, fortunately, although while let allows you to write this neatly:

 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); } } 

NB. that I believe that axis should be canceled to update the neighbour . (I did not compile this, but I hope it is close.)

Theoretically, the latter offers approximately 2 x acceleration compared to any other offers so far. At least this reduces the number of calculations of angle , axis and force from n * (n - 1) to n * (n - 1) / 2 .

+8
source

I found the answer to my question

 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); } } 

By creating a separate vector for holding movements, instead of making the movement a sign of a point, I can take the vector of points twice as unchanged and borrow the motion vector once as mutable.

I think that C ++ thinking is still thinking too much about classes, but I can still be mistaken that this is the perfect way to make such a drawing in rust. If anyone has a problem with this solution or just knows how best to do it, any suggestions would be great.

+1
source

Source: https://habr.com/ru/post/985923/


All Articles