first commit

This commit is contained in:
mii443
2025-07-11 23:38:31 +09:00
commit 65010f0cfd
8 changed files with 566 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

154
Cargo.lock generated Normal file
View File

@ -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",
]

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "practical_irp"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }

64
src/client.rs Normal file
View File

@ -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());
}
}
}

133
src/irp.rs Normal file
View File

@ -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<Self, String> {
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<Vec<u64>>,
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<u64> {
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<Grid> {
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)));
}
}

7
src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
pub mod irp;
pub mod client;
pub mod server;
pub use irp::*;
pub use client::Client;
pub use server::Server;

68
src/main.rs Normal file
View File

@ -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
);
}
}
}

131
src/server.rs Normal file
View File

@ -0,0 +1,131 @@
use crate::{estimate_possible_grids, Grid, Matrix, Observation};
use std::collections::HashMap;
pub struct Server {
matrix: Matrix,
heatmap: HashMap<Grid, f64>,
}
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<Grid, f64> {
&self.heatmap
}
pub fn get_normalized_heatmap(&self) -> HashMap<Grid, f64> {
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));
}
}