-
-
Save ajparsons/ce29bcbf12e6c0595e298905502f5a77 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Install rextendr if you haven't | |
| # install.packages("rextendr") | |
| library(rextendr) | |
| rust_code <- " | |
| use extendr_api::prelude::*; | |
| use extendr_api::prelude::*; | |
| use statrs::distribution::{Normal, Continuous}; | |
| /// Midpoint-based approach to computing expected distance to nearest party | |
| /// | |
| /// This divides the voter space into segments between party midpoints, | |
| /// then integrates the expected distance to the nearest party in each segment. | |
| #[extendr] | |
| fn mean_min_abs_distance_midpoints(parties: Vec<f64>) -> f64 { | |
| // Return NaN if no parties provided | |
| if parties.is_empty() { | |
| return f64::NAN; | |
| } | |
| // Create the standard normal distribution | |
| let norm = Normal::new(0.0, 1.0).unwrap(); | |
| // Sort the party positions | |
| let mut sorted_parties = parties.clone(); | |
| sorted_parties.sort_by(|a, b| a.partial_cmp(b).unwrap()); | |
| let mut total = 0.0; | |
| // Extend the party list with artificial outer boundaries | |
| // so we handle voters beyond the outermost parties | |
| let mut boundaries = Vec::new(); | |
| // Add a lower bound far to the left (say, -∞ → party 0 dominates) | |
| boundaries.push(f64::NEG_INFINITY); | |
| for i in 0..(sorted_parties.len() - 1) { | |
| // Midpoint between party i and i+1 | |
| let mid = (sorted_parties[i] + sorted_parties[i + 1]) / 2.0; | |
| boundaries.push(mid); | |
| } | |
| // Add upper bound far to the right (∞ → last party dominates) | |
| boundaries.push(f64::INFINITY); | |
| // Now integrate each segment where one party is closest | |
| for i in 0..sorted_parties.len() { | |
| let left = boundaries[i]; | |
| let right = boundaries[i + 1]; | |
| let party_pos = sorted_parties[i]; | |
| // Numerical integration in this segment | |
| let num_points = 1000; | |
| let step = (right - left) / num_points as f64; | |
| let mut segment_sum = 0.0; | |
| for j in 0..num_points { | |
| let x = left + j as f64 * step; | |
| // If x is infinite (at the far ends), skip it | |
| if !x.is_finite() { | |
| continue; | |
| } | |
| let pdf = norm.pdf(x); | |
| let distance = (x - party_pos).abs(); | |
| // Add to segment's contribution | |
| segment_sum += distance * pdf * step; | |
| } | |
| total += segment_sum; | |
| } | |
| total | |
| } | |
| extendr_module! { | |
| mod mymodule; | |
| fn mean_min_abs_distance_midpoints; | |
| } | |
| " | |
| # Compile and load | |
| rust_source( | |
| code = rust_code, | |
| dependencies = list( | |
| statrs = "0.16", | |
| rayon = "1.6" | |
| ), | |
| quiet = TRUE, | |
| profile = "release" | |
| ) | |
| library(tictoc) | |
| # Example usage | |
| set.seed(42) | |
| party_positions <- rnorm(5) | |
| cat("Party positions:\n") | |
| print(party_positions) | |
| tic() | |
| # Compute the exact mean minimal absolute distance | |
| result <- mean_min_abs_distance_midpoints(party_positions) | |
| toc() | |
| cat("Mean minimal absolute distance (exact, no sampling):\n") | |
| print(result) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment