First of all, I am happy to see someone implementing a numeric tool in Rust!
Then I will try to avoid unnessary double changes:
Your max_min_ functions could indeed be refactored:
// instead calculate max/min in all three coordinates.
// this should be more efficient (cache local)
// careful with empty input!
pub fn max_min(particles: &[Particle]) -> (Point3, Point3) {
use std::f64;
assert!(particles.len() > 0);
// every value will be higher than this one
let mut max = Point3::new(f64::MIN);
// every value will be smaller
let mut min = Point3::new(f64::MAX);
// could use some magic ... (for c in xyz)
for p in particles {
if p.x > max.x { max.x = p.x; };
if p.y > max.y { max.y = p.y; };
if p.z > max.z { max.z = p.z; };
if p.x < min.x { min.x = p.x; };
if p.y < min.y { min.y = p.y; };
if p.z < min.z { min.z = p.z; };
}
(max, min)
}
Leading to less code in theta_exeeded.
fn theta_exceeded:
let x_distance = (max_min.0 - x_max_min.1).abs();
Instead of storing the result of max_min in a single variable, you can unpack them:
let (max, min) = max_min(&node.points.as_ref().unwrap());
The multible occurences of ...
let d_over_d_cubed = (d_vector.0 / d_magnitude.powf(2.0),
d_vector.1 / d_magnitude.powf(2.0),
d_vector.2 / d_magnitude.powf(2.0));
could be simplified by implementing the Div operator for Point3 and using that type whenever you are using (f64, f64, f64) now.
use std::ops::Div;
impl Div<f64> for Point3 {
type Output = Point3;
fn div(self, rhs: f64) -> Point3 {
Point3 {
x: self.x / rhs,
y: self.y / rhs,
z: self.z / rhs
}
}
}
... allows to write ...
let d_over_d_cubed = d_vector / d_magnitude.powf(2.0);
Implementing the std::ops::Mul Trait aswell has similar effects.
Maybe Vec3 would be a better name instead of Point3.
The next pattern that could be optimized is:
let d_magnitude = particle.distance(&node_as_particle);
let d_vector = particle.distance_vector(&node_as_particle);
As far as I know the following should work as well:
let d_vector = particle.distance_vector(&node_as_particle);
let d_magnitude = d_vector.abs();
You could even write
fn distance(&a: Point3, &b: Point3) -> (Point3, f64)
returning d_vector and d_magnitude at once.
In fn particle_gravity the replacement of acceleration_total: (f64, f64, f64)
with acceleration: Point3 also eliminates the .clone() .
In fn particle_gravity you can collapse both arms using the following:
for side in &[&node.left, &node.left] { match side { ... } }
The inner & turns the values into references and the outer turns the array into a slice, so you can iterate over it.
In fn new_root_node you can use the following construct:
let (split_value, split_dimension) = {
if zdistance > ydistance && zdistance > xdistance {
find_median_z(pts, start, end, mid)
} else if ydistance > xdistance && ydistance > zdistance {
find_median_y(pts, start, end, mid)
} else {
find_median_x(pts, start, end, mid)
}
}
root_node.split_dimension = split_dimension;
root_node.split_value = split_value;
I am going to stop here for today/tonight, but I hope to have given you a few concepts to
rustify/simplify the code.
greetings