From 65010f0cfd6cb460a4e73956d5c81ee6cd553913 Mon Sep 17 00:00:00 2001 From: mii443 Date: Fri, 11 Jul 2025 23:38:31 +0900 Subject: [PATCH] first commit --- .gitignore | 1 + Cargo.lock | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 8 +++ src/client.rs | 64 +++++++++++++++++++++ src/irp.rs | 133 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 7 +++ src/main.rs | 68 ++++++++++++++++++++++ src/server.rs | 131 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 566 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/client.rs create mode 100644 src/irp.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/server.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d735c9d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,154 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "practical_irp" +version = "0.1.0" +dependencies = [ + "rand", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8c64daf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "practical_irp" +version = "0.1.0" +edition = "2021" + +[dependencies] +rand = "0.8" +serde = { version = "1.0", features = ["derive"] } diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..b7c2360 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,64 @@ +use crate::{Grid, IrpParams, Matrix, Observation}; +use rand::Rng; + +pub struct Client { + matrix: Matrix, +} + +impl Client { + pub fn new(matrix: Matrix) -> Self { + Self { matrix } + } + + pub fn generate_observation(&self, grid: Grid) -> Observation { + let mut rng = rand::thread_rng(); + let max_i = self.matrix.params.matrix_rows(); + let i = rng.gen_range(0..max_i) as u32; + + let v = self + .matrix + .get(i as usize, grid.0) + .expect("Invalid grid or index"); + + Observation { v, i } + } + + pub fn params(&self) -> &IrpParams { + &self.matrix.params + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_observation_generation() { + let params = IrpParams::new(16, 4, 10).unwrap(); + let matrix = Matrix::generate_random(params.clone()); + let client = Client::new(matrix.clone()); + + let grid = Grid::new(5); + let obs = client.generate_observation(grid); + + assert!(obs.i < params.matrix_rows() as u32); + assert_eq!(obs.v, matrix.get(obs.i as usize, grid.0).unwrap()); + } + + #[test] + fn test_multiple_observations() { + let params = IrpParams::new(16, 4, 10).unwrap(); + let matrix = Matrix::generate_random(params); + let client = Client::new(matrix); + + let grid = Grid::new(3); + let observations: Vec<_> = (0..100) + .map(|_| client.generate_observation(grid)) + .collect(); + + for obs in &observations { + assert!(obs.i < 16); + assert!(obs.v < client.params().modulus()); + } + } +} diff --git a/src/irp.rs b/src/irp.rs new file mode 100644 index 0000000..cf330fa --- /dev/null +++ b/src/irp.rs @@ -0,0 +1,133 @@ +use rand::Rng; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IrpParams { + pub b: u32, // 送信ビット数 + pub m: u32, // m < B + pub d: u32, // グリッド数 +} + +impl IrpParams { + pub fn new(b: u32, m: u32, d: u32) -> Result { + if m >= b { + return Err("m must be less than B".to_string()); + } + if b > 64 { + return Err("B must be less than or equal to 64".to_string()); + } + Ok(Self { b, m, d }) + } + + pub fn modulus(&self) -> u64 { + 1u64 << (self.b - self.m) + } + + pub fn matrix_rows(&self) -> usize { + 1usize << self.m + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Matrix { + pub data: Vec>, + pub params: IrpParams, +} + +impl Matrix { + pub fn generate_random(params: IrpParams) -> Self { + let mut rng = rand::thread_rng(); + let rows = params.matrix_rows(); + let cols = params.d as usize; + let modulus = params.modulus(); + + let data = (0..rows) + .map(|_| { + (0..cols) + .map(|_| rng.gen_range(0..modulus)) + .collect() + }) + .collect(); + + Self { data, params } + } + + pub fn get(&self, i: usize, g: u32) -> Option { + if i >= self.data.len() || g >= self.params.d { + return None; + } + Some(self.data[i][g as usize]) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Observation { + pub v: u64, + pub i: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Grid(pub u32); + +impl Grid { + pub fn new(g: u32) -> Self { + Self(g) + } +} + +pub fn estimate_possible_grids(matrix: &Matrix, observation: &Observation) -> HashSet { + let mut possible_grids = HashSet::new(); + + for g in 0..matrix.params.d { + if let Some(v) = matrix.get(observation.i as usize, g) { + if v == observation.v { + possible_grids.insert(Grid::new(g)); + } + } + } + + possible_grids +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_irp_params() { + assert!(IrpParams::new(32, 8, 1000).is_ok()); + assert!(IrpParams::new(32, 32, 1000).is_err()); + assert!(IrpParams::new(65, 8, 1000).is_err()); + } + + #[test] + fn test_matrix_generation() { + let params = IrpParams::new(32, 8, 100).unwrap(); + let matrix = Matrix::generate_random(params.clone()); + + assert_eq!(matrix.data.len(), 256); // 2^8 + assert_eq!(matrix.data[0].len(), 100); + + for row in &matrix.data { + for &val in row { + assert!(val < params.modulus()); + } + } + } + + #[test] + fn test_grid_estimation() { + let params = IrpParams::new(16, 4, 10).unwrap(); + let mut matrix = Matrix::generate_random(params); + + matrix.data[5][3] = 42; + matrix.data[5][7] = 42; + + let obs = Observation { v: 42, i: 5 }; + let possible = estimate_possible_grids(&matrix, &obs); + + assert!(possible.contains(&Grid::new(3))); + assert!(possible.contains(&Grid::new(7))); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3e7a0b4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,7 @@ +pub mod irp; +pub mod client; +pub mod server; + +pub use irp::*; +pub use client::Client; +pub use server::Server; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7465070 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,68 @@ +use practical_irp::{Client, Grid, IrpParams, Matrix, Server}; + +fn index_pair_to_grid(a: u32, b: u32, size: u32) -> Grid { + Grid::new(a * size + b) +} + +fn grid_to_index_pair(grid: &Grid, size: u32) -> (u32, u32) { + let index = grid.0; + (index / size, index % size) +} + +fn main() { + let size = 31u32; + let params = IrpParams::new(16, 8, size.pow(2).pow(2)).expect("Invalid parameters"); + println!("IRP Parameters:"); + println!(" B (transmission bits): {}", params.b); + println!(" m: {}", params.m); + println!(" d (number of grids): {}", params.d); + println!(" Modulus: {}", params.modulus()); + println!(" Matrix rows: {}", params.matrix_rows()); + println!(); + + let matrix = Matrix::generate_random(params); + + let mut server = Server::new(matrix.clone()); + let client = Client::new(matrix); + + println!("Simulating client observations..."); + let true_grids = vec![ + index_pair_to_grid(7, 7, size), + index_pair_to_grid(8, 8, size), + index_pair_to_grid(15, 20, size), + ]; + + for &grid in &true_grids { + for _ in 0..255 { + let observation = client.generate_observation(grid); + server.process_observation(&observation); + } + println!( + " Generated 10 observations for grid {:?}", + grid_to_index_pair(&grid, size) + ); + } + + println!("\nTop 5 estimated grids:"); + let top_grids = server.get_top_grids(5); + for (i, (grid, score)) in top_grids.iter().enumerate() { + println!( + " {}. Grid {:?} - score: {:.4}", + i + 1, + grid_to_index_pair(grid, size), + score + ); + } + + println!("\nNormalized heatmap for top grids:"); + let normalized = server.get_normalized_heatmap(); + for (grid, _count) in &top_grids { + if let Some(&prob) = normalized.get(grid) { + println!( + " Grid {:?} - probability: {:.4}", + grid_to_index_pair(grid, size), + prob + ); + } + } +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..58d6b76 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,131 @@ +use crate::{estimate_possible_grids, Grid, Matrix, Observation}; +use std::collections::HashMap; + +pub struct Server { + matrix: Matrix, + heatmap: HashMap, +} + +impl Server { + pub fn new(matrix: Matrix) -> Self { + Self { + matrix, + heatmap: HashMap::new(), + } + } + + pub fn process_observation(&mut self, observation: &Observation) { + let possible_grids = estimate_possible_grids(&self.matrix, observation); + let num_candidates = possible_grids.len() as f64; + + if num_candidates > 0.0 { + let increment = 1.0; + for grid in possible_grids { + *self.heatmap.entry(grid).or_insert(0.0) += increment; + } + } + } + + pub fn process_observations(&mut self, observations: &[Observation]) { + for obs in observations { + self.process_observation(obs); + } + } + + pub fn get_heatmap(&self) -> &HashMap { + &self.heatmap + } + + pub fn get_normalized_heatmap(&self) -> HashMap { + let total: f64 = self.heatmap.values().sum(); + if total == 0.0 { + return HashMap::new(); + } + + self.heatmap + .iter() + .map(|(&grid, &score)| (grid, score / total)) + .collect() + } + + pub fn get_top_grids(&self, n: usize) -> Vec<(Grid, f64)> { + let mut grids: Vec<_> = self + .heatmap + .iter() + .map(|(&grid, &score)| (grid, score)) + .collect(); + + grids.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + grids.truncate(n); + grids + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::IrpParams; + + #[test] + fn test_server_aggregation() { + let params = IrpParams::new(16, 4, 10).unwrap(); + let mut matrix = Matrix::generate_random(params); + + matrix.data[2][5] = 100; + matrix.data[7][5] = 100; + matrix.data[2][8] = 100; + + let mut server = Server::new(matrix); + + server.process_observation(&Observation { v: 100, i: 2 }); + server.process_observation(&Observation { v: 100, i: 7 }); + + let heatmap = server.get_heatmap(); + assert_eq!(heatmap[&Grid::new(5)], 1.0); // 2回観測、各回1/2を加算 + assert_eq!(heatmap[&Grid::new(8)], 0.5); // 1回観測、1/2を加算 + } + + #[test] + fn test_normalized_heatmap() { + let params = IrpParams::new(16, 4, 10).unwrap(); + let mut matrix = Matrix::generate_random(params); + + // 固定の値を設定して予測可能にする + matrix.data[0][5] = 10; + matrix.data[1][7] = 20; + matrix.data[2][3] = 30; + + let mut server = Server::new(matrix); + + let observations = vec![ + Observation { v: 10, i: 0 }, + Observation { v: 20, i: 1 }, + Observation { v: 30, i: 2 }, + ]; + + server.process_observations(&observations); + + let normalized = server.get_normalized_heatmap(); + if !normalized.is_empty() { + let total: f64 = normalized.values().sum(); + assert!((total - 1.0).abs() < 1e-10); + } + } + + #[test] + fn test_top_grids() { + let params = IrpParams::new(16, 4, 10).unwrap(); + let matrix = Matrix::generate_random(params); + let mut server = Server::new(matrix); + + server.heatmap.insert(Grid::new(1), 10.0); + server.heatmap.insert(Grid::new(2), 20.0); + server.heatmap.insert(Grid::new(3), 5.0); + server.heatmap.insert(Grid::new(4), 15.0); + + let top = server.get_top_grids(2); + assert_eq!(top.len(), 2); + assert_eq!(top[0], (Grid::new(2), 20.0)); + assert_eq!(top[1], (Grid::new(4), 15.0)); + } +}