2015-04-25 14 views
6

Sto provando a scrivere un programma in modo uniforme (o il più vicino possibile) per distribuire punti sulla superficie di una sfera. Sto cercando di ottenere questo mettendo N punti casualmente su una sfera unitaria, e quindi eseguendo più passaggi in cui i punti si respingono a vicenda.Mutare un oggetto all'interno di loop nidificati

Il problema è nel loop sull'array dei punti. Il codice sottostante scorre su ogni punto, quindi il ciclo all'interno di esso, di nuovo scorre su ogni punto e calcola la forza repulsiva tra ogni coppia di punti.

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

sto ottenendo l'errore:

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 { 

e la spiegazione

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

Sono abbastanza nuovo per Rust, proveniente da un C++ fondo, e ho non ho idea di come far funzionare questo tipo di pattern in Rust.

Qualsiasi soluzione a questo sarebbe molto apprezzata, dato che ora sono assolutamente bloccato per le idee. Grazie.

Edit:

Ho elaborato una soluzione, che ho descritto in una risposta di seguito. Ancora non so se è il modo migliore per risolvere il problema, quindi sono ancora aperto a suggerimenti.

+2

'points.iter_mut()' potrebbe essere scritto 'e mut points'. –

+2

Certamente l'idea fondamentale che si desidera raggiungere non può essere eseguita in un codice Rust sicuro; le cose in lettura e in scrittura dovranno essere separate in qualche modo per renderle espresse in un codice sicuro. –

risposta

1

ho trovato una risposta alla mia domanda

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

Con la creazione di un vettore separata per contenere i movimenti, piuttosto che avere il movimento sia un tratto del punto, posso prendere in prestito il vettore di punti due volte più immutabile e prendere in prestito i movimenti una volta tanto mutevole.

Penso che una mentalità C++ mi faccia ancora pensare troppo in termini di classi, ma potrei comunque sbagliarmi sul fatto che questo è un modo ideale per affrontare questo tipo di modello in ruggine. Se qualcuno ha un problema con questa soluzione, o semplicemente conosce un modo migliore per farlo, qualsiasi suggerimento sarebbe ottimo.

8

Ci sono alcuni modi in cui si può scrivere questo.

Uno è la suggestione, di separare movimenti in un vettore separato, dal momento che assicura che il prestito mutevole del valore movimento non forzare il compilatore di impedire conservativamente accesso al resto di points evitare prende in prestito mutevoli da aliasing. In Rust, &mut il riferimento non può mai alias: se i valori sono accessibili esattamente da un percorso, è garantito che tutte le/tutte le mutazioni saranno protette dalla memoria, se c'è un alias ci vuole più sforzo (inclusi i controlli di runtime) per garantire che le mutazioni siano sicure .

altro è di usare indicies per l'anello esterno:

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 
} 

Un terzo metodo consiste nel cambiare come i loop funzionano: se si considera l'interazione tra un punto e il suo vicino, aggiornare sia il punto e il prossimo allo stesso tempo. Ciò consente di effettuare una iterazione "triangolare": il punto 0 interagisce con 1..n, il punto 1 interagisce con 2..n, ... (dove n = points.len()). Questo può essere fatto in modo che il compilatore capisca non alias.

Prima di tutto è necessario reimpostare tutti movement su 1. I loop principali sono quindi costituiti da un loop esterno che seleziona il singolo elemento, quindi uno può "rimettere in discussione" l'iteratore per il ciclo interno. Il ciclo esterno non può utilizzare for, dal momento che for avrà la proprietà del iteratore, per fortuna, però, while let permette di scrivere questo ordinatamente:

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. che credo che axis debba essere annullato per l'aggiornamento neighbour. (Non l'ho compilato, ma spero sia vicino.)

In teoria, quest'ultimo offre un aumento di circa 2 × rispetto a qualsiasi altro suggerimento finora. Almeno, riduce il numero di calcoli di angle, axis & force da n * (n - 1) a n * (n - 1)/2.

Problemi correlati