petabi / petal-clustering

DBSCAN and OPTICS clustering algorithms.
Apache License 2.0
30 stars 4 forks source link

Custom Distance Metric Implementation Example #59

Closed TurtIeSocks closed 2 years ago

TurtIeSocks commented 2 years ago

Hi, sorry for the dumb question, I'm new to rust and algorithms in general but I'm trying to implement a distance metric using lat, lon coordinates to return the number of meters:

    fn earth_distance(start: [f64; 2], end: [f64; 2]) -> f64 {
        let d_lat: f64 = (end[0] - start[0]).to_radians();
        let d_lon: f64 = (end[1] - start[1]).to_radians();
        let lat1: f64 = (start[0]).to_radians();
        let lat2: f64 = (end[0]).to_radians();

        let a: f64 = ((d_lat / 2.0).sin()) * ((d_lat / 2.0).sin())
            + ((d_lon / 2.0).sin()) * ((d_lon / 2.0).sin()) * (lat1.cos()) * (lat2.cos());
        let c: f64 = 2.0 * ((a.sqrt()).atan2((1.0 - a).sqrt()));

        6.371 * c
    }

I notcied that petal-clustering has support for custom metric functions, but it's not clear how to implement this in my code. Any advice would be greatly appreciated, thank you!

minshao commented 2 years ago

Basically, Metric is a trait defined in petal-neighbors crate, you just need to implement the trait so that functions requires it could use it:

use petal_neighbors::distance::Metric;
use ndarray::{Array2, ArrayView1, ArrayView2};

struct EarthDistance {}

impl<A> Metric<A> for EarthDistance
where
    A: Float + Zero + AddAssign,
{
    fn distance(&self, start: &ArrayView1<A>, end: &ArrayView1<A>) -> A {
         // your definition of the distance here
        let d_lat: f64 = (end[0] - start[0]).to_radians();
        let d_lon: f64 = (end[1] - start[1]).to_radians();
        let lat1: f64 = (start[0]).to_radians();
        let lat2: f64 = (end[0]).to_radians();

        let a: f64 = ((d_lat / 2.0).sin()) * ((d_lat / 2.0).sin())
            + ((d_lon / 2.0).sin()) * ((d_lon / 2.0).sin()) * (lat1.cos()) * (lat2.cos());
        let c: f64 = 2.0 * ((a.sqrt()).atan2((1.0 - a).sqrt()));

        6.371 * c
    }

    fn rdistance(&self, _: &ArrayView1<A>, _: &ArrayView1<A>) -> A {
         // required by the trait, even if you are not using it, still need to implement it.
         A::zero();
    }

    fn rdistance_to_distance(&self, _: A) -> A {
         A::zero();
    }

    fn distance_to_rdistance(&self, _: A) -> A {
         A::zero();
    }
}

The above is not tested, so you might still (or definitely will) need to make adjustments. Hope it would suffice as an example. And let me know if you have further questions~

TurtIeSocks commented 2 years ago

Sorry for the delay, but I really appreciate the example! I'm still figuring some things out on my end, but this is mostly due to some core Rust knowledge that I need to work on. Thank you!