use std::path::PathBuf;
use plotters::prelude::*;
use haversine_rs::point::Point;
use haversine_rs::units::Unit;
use haversine_rs::distance;
use std::collections::HashMap;
pub struct EmpiricalVariogram {
pub distances: Vec<f64>,
pub semivariances: Vec<f64>
}
impl EmpiricalVariogram {
pub fn new(
longitudes: &Vec<f64>,
latitudes: &Vec<f64>,
values: &Vec<f64>
) -> Self {
let mut points = vec![];
for i in 0..longitudes.len() {
if let (Some(lon), Some(lat), Some(value)) = (longitudes.get(i), latitudes.get(i), values.get(i)) {
points.push((Point::new(*lat, *lon), value));
}
}
let mut distances = vec![];
for i in 0..points.len() {
for j in (i + 1)..points.len() {
let (p1, _) = &points[i];
let (p2, _) = &points[j];
let dist = distance(*p1, *p2, Unit::Meters);
distances.push(dist);
}
}
let max_distance = distances
.iter()
.cloned()
.fold(0.0 / 0.0, f64::max); let max_dist = 0.5 * max_distance;
let num_bins = 15;
let bin_size = max_dist / num_bins as f64;
let mut semivariance_bins: HashMap<usize, (f64, usize)> = HashMap::new();
for i in 0..points.len() {
for j in (i + 1)..points.len() {
let (p1, t1) = points[i];
let (p2, t2) = points[j];
let dist = distance(p1, p2, Unit::Meters);
if dist <= max_dist {
let bin = (dist / bin_size).floor() as usize;
let gamma = 0.5 * (t1 - t2).powi(2);
let entry = semivariance_bins.entry(bin).or_insert((0.0, 0));
entry.0 += gamma;
entry.1 += 1;
}
}
}
let mut binned_distances = vec![];
let mut binned_semivariances = vec![];
let mut sorted_bins: Vec<_> = semivariance_bins.into_iter().collect();
sorted_bins.sort_by_key(|&(bin, _)| bin);
for (bin, (sum, count)) in sorted_bins {
if count > 0 {
let center = (bin as f64 + 0.5) * bin_size;
let gamma = sum / count as f64;
binned_distances.push(center);
binned_semivariances.push(gamma);
}
}
EmpiricalVariogram { distances: binned_distances, semivariances: binned_semivariances }
}
pub fn get_max_distance(&self) -> f64 {
self.distances.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
}
pub fn get_max_semivariance(&self) -> f64 {
self.semivariances.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
}
}
pub enum VariogramModel {
Spherical,
Exponential,
Gaussian
}
pub struct Variogram {
pub nugget: f64,
pub sill: f64,
pub range: f64,
pub model: VariogramModel,
}
impl Variogram {
pub fn new(nugget: f64, sill: f64, range: f64, model: VariogramModel) -> Self {
Variogram { nugget, sill, range, model }
}
pub fn semivariance(&self, h: f64) -> f64 {
match self.model {
VariogramModel::Spherical => {
if h <= self.range {
let hr = h / self.range;
self.nugget + (self.sill - self.nugget) * (1.5 * hr - 0.5 * hr.powi(3))
} else {
self.sill
}
}
VariogramModel::Exponential => {
self.nugget + (self.sill - self.nugget) * (1.0 - (-h / self.range).exp())
}
VariogramModel::Gaussian => {
self.nugget + (self.sill - self.nugget) * (1.0 - (- (h / self.range).powi(2)).exp())
}
}
}
fn compute_cost(&self, x_data: &[f64], y_data: &[f64]) -> f64 {
x_data.iter().zip(y_data.iter())
.map(|(&h, &y)| {
let model = self.semivariance(h);
(y - model).powi(2)
})
.sum()
}
fn compute_gradient(&mut self, x_data: &[f64], y_data: &[f64]) -> (f64, f64, f64) {
let mut grad_nugget = 0.0;
let mut grad_sill = 0.0;
let mut grad_range = 0.0;
for (&h, &y) in x_data.iter().zip(y_data.iter()) {
let model_value = self.semivariance(h);
let diff = model_value - y;
match self.model {
VariogramModel::Spherical => {
if h <= self.range {
let hr = h / self.range;
let common = 1.5 * hr - 0.5 * hr.powi(3);
grad_nugget += diff * 1.0;
grad_sill += diff * common;
let d_common_d_range = (-1.5 * h / self.range.powi(2)) + (1.5 * h.powi(3) / self.range.powi(4));
grad_range += diff * (self.sill - self.nugget) * d_common_d_range;
} else {
grad_nugget += 0.0;
grad_sill += diff * 1.0;
grad_range += 0.0;
}
}
VariogramModel::Exponential => {
let exp_term = (-h / self.range).exp();
let factor = self.sill - self.nugget;
grad_nugget += diff * 1.0;
grad_sill += diff * (1.0 - exp_term);
grad_range += diff * factor * (h / self.range.powi(2)) * exp_term;
}
VariogramModel::Gaussian => {
let exp_term = (- (h / self.range).powi(2)).exp();
grad_nugget += diff;
grad_sill += diff * (1.0 - exp_term);
grad_range += diff * (self.sill - self.nugget) * (-2.0 * h / self.range.powi(3)) * exp_term;
}
}
}
(grad_nugget, grad_sill, grad_range)
}
pub fn fit(&mut self, empirical_variogram: &EmpiricalVariogram, learning_rate: f64, max_iter: usize, tol: f64) {
let x_data = &empirical_variogram.distances;
let y_data = &empirical_variogram.semivariances;
let mut prev_cost = f64::INFINITY;
for iter in 0..max_iter {
let cost = self.compute_cost(x_data, y_data);
if (prev_cost - cost).abs() < tol {
println!("Converged after {} iterations", iter);
break;
}
let (nugget_grad, sill_grad, range_grad) = self.compute_gradient(x_data, y_data);
self.nugget -= learning_rate * nugget_grad;
self.sill -= learning_rate * sill_grad;
self.range -= learning_rate * range_grad;
prev_cost = cost;
if iter % 10 == 0 {
println!("Iteration {}: Cost = {:.6}", iter, cost);
}
}
}
}
pub fn plot(variogram: &Variogram, empirical_variogram: &EmpiricalVariogram, file_path: &PathBuf) {
let max_x_data = empirical_variogram.get_max_distance();
let max_y_data = empirical_variogram.get_max_semivariance();
let root = BitMapBackend::new(&file_path, (640, 480)).into_drawing_area();
root.fill(&WHITE).unwrap();
let mut chart = ChartBuilder::on(&root)
.caption("Semivariogram", ("Arial", 20))
.x_label_area_size(50)
.y_label_area_size(50)
.build_cartesian_2d(0.0..max_x_data, 0.0..max_y_data)
.unwrap();
chart.configure_mesh()
.x_desc("Lag Distance (h)") .y_desc("Semivariance") .draw()
.unwrap();
let points: Vec<(f64, f64)> = empirical_variogram.distances.iter()
.copied()
.zip(empirical_variogram.semivariances.iter().copied())
.collect();
chart.draw_series(
points.iter().map(|&(x, y)| {
Circle::new((x, y), 5,
ShapeStyle {
color: BLUE.to_rgba(),
filled: true,
stroke_width: 2,
})
})
).unwrap();
chart.draw_series(LineSeries::new(
(0..max_x_data as usize)
.map(|i| (i as f64, variogram.semivariance(i as f64))),
&RED,
)).unwrap();
root.present().unwrap();
}