1#![allow(dead_code)]
18
19use anyhow::{anyhow, Result};
20use scirs2_core::random::StdRng; use scirs2_core::random::*; use serde::{Deserialize, Serialize};
23use std::collections::HashMap;
24use trustformers_core::tensor::Tensor;
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct FedAvgConfig {
29 pub local_epochs: usize,
31 pub local_learning_rate: f32,
33 pub client_fraction: f32,
35 pub min_clients: usize,
37 pub max_clients: usize,
39 pub weight_decay: f32,
41}
42
43impl Default for FedAvgConfig {
44 fn default() -> Self {
45 Self {
46 local_epochs: 5,
47 local_learning_rate: 1e-3,
48 client_fraction: 0.1,
49 min_clients: 2,
50 max_clients: 100,
51 weight_decay: 0.0,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct FedProxConfig {
59 pub fedavg_config: FedAvgConfig,
61 pub mu: f32,
63}
64
65impl Default for FedProxConfig {
66 fn default() -> Self {
67 Self {
68 fedavg_config: FedAvgConfig::default(),
69 mu: 0.01,
70 }
71 }
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct DifferentialPrivacyConfig {
77 pub epsilon: f32,
79 pub delta: f32,
81 pub sensitivity: f32,
83 pub noise_mechanism: NoiseMechanism,
85}
86
87impl Default for DifferentialPrivacyConfig {
88 fn default() -> Self {
89 Self {
90 epsilon: 1.0,
91 delta: 1e-5,
92 sensitivity: 1.0,
93 noise_mechanism: NoiseMechanism::Gaussian,
94 }
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub enum NoiseMechanism {
101 Gaussian,
103 Laplace,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub enum ClientSelectionStrategy {
110 Random,
112 DataSize,
114 ComputeCapacity,
116 CommunicationQuality,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct ClientInfo {
123 pub client_id: String,
125 pub data_size: usize,
127 pub compute_capacity: f32,
129 pub communication_quality: f32,
131 pub available: bool,
133}
134
135#[derive(Debug)]
140pub struct FedAvg {
141 config: FedAvgConfig,
142 global_parameters: Vec<Tensor>,
143 client_weights: HashMap<String, f32>,
144 current_round: usize,
145 selected_clients: Vec<String>,
146 rng: StdRng,
147}
148
149impl FedAvg {
150 pub fn new(config: FedAvgConfig) -> Self {
152 Self {
153 config,
154 global_parameters: Vec::new(),
155 client_weights: HashMap::new(),
156 current_round: 0,
157 selected_clients: Vec::new(),
158 rng: StdRng::seed_from_u64(42),
159 }
160 }
161
162 pub fn initialize_global_parameters(&mut self, parameters: Vec<Tensor>) {
164 self.global_parameters = parameters;
165 }
166
167 pub fn select_clients(
169 &mut self,
170 available_clients: &[ClientInfo],
171 strategy: ClientSelectionStrategy,
172 ) -> Result<Vec<String>> {
173 let available: Vec<&ClientInfo> =
174 available_clients.iter().filter(|c| c.available).collect();
175
176 if available.is_empty() {
177 return Err(anyhow!("No available clients"));
178 }
179
180 let num_clients = (available.len() as f32 * self.config.client_fraction).round() as usize;
181 let num_clients = num_clients
182 .max(self.config.min_clients)
183 .min(self.config.max_clients)
184 .min(available.len());
185
186 let selected = match strategy {
187 ClientSelectionStrategy::Random => {
188 let mut indices: Vec<usize> = (0..available.len()).collect();
189 for i in 0..num_clients {
190 let j = self.rng.random_range(i..indices.len());
191 indices.swap(i, j);
192 }
193 indices[..num_clients].iter().map(|&i| available[i].client_id.clone()).collect()
194 },
195 ClientSelectionStrategy::DataSize => {
196 let mut clients_with_size: Vec<_> =
197 available.iter().map(|c| (c.client_id.clone(), c.data_size)).collect();
198 clients_with_size.sort_by_key(|(_, size)| std::cmp::Reverse(*size));
199 clients_with_size[..num_clients].iter().map(|(id, _)| id.clone()).collect()
200 },
201 ClientSelectionStrategy::ComputeCapacity => {
202 let mut clients_with_capacity: Vec<_> =
203 available.iter().map(|c| (c.client_id.clone(), c.compute_capacity)).collect();
204 clients_with_capacity.sort_by(|(_, a), (_, b)| {
205 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
206 });
207 clients_with_capacity[..num_clients].iter().map(|(id, _)| id.clone()).collect()
208 },
209 ClientSelectionStrategy::CommunicationQuality => {
210 let mut clients_with_quality: Vec<_> = available
211 .iter()
212 .map(|c| (c.client_id.clone(), c.communication_quality))
213 .collect();
214 clients_with_quality.sort_by(|(_, a), (_, b)| {
215 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
216 });
217 clients_with_quality[..num_clients].iter().map(|(id, _)| id.clone()).collect()
218 },
219 };
220
221 self.selected_clients = selected;
222 Ok(self.selected_clients.clone())
223 }
224
225 pub fn aggregate_updates(
227 &mut self,
228 client_updates: HashMap<String, Vec<Tensor>>,
229 ) -> Result<Vec<Tensor>> {
230 if client_updates.is_empty() {
231 return Err(anyhow!("No client updates to aggregate"));
232 }
233
234 let total_weight: f32 = client_updates
235 .keys()
236 .map(|client_id| self.client_weights.get(client_id).unwrap_or(&1.0))
237 .sum();
238
239 if total_weight == 0.0 {
240 return Err(anyhow!("Total client weight is zero"));
241 }
242
243 let param_count = client_updates
245 .values()
246 .next()
247 .ok_or_else(|| anyhow::anyhow!("client_updates must have at least one entry"))?
248 .len();
249 let mut aggregated = Vec::with_capacity(param_count);
250
251 for i in 0..param_count {
252 let first_param = &client_updates
254 .values()
255 .next()
256 .ok_or_else(|| anyhow::anyhow!("client_updates must have at least one entry"))?[i];
257 aggregated.push(Tensor::zeros_like(first_param)?);
258 }
259
260 for (client_id, updates) in &client_updates {
262 let weight = self.client_weights.get(client_id).unwrap_or(&1.0) / total_weight;
263
264 for (i, update) in updates.iter().enumerate() {
265 let weighted_update = update.mul_scalar(weight)?;
266 aggregated[i] = aggregated[i].add(&weighted_update)?;
267 }
268 }
269
270 self.global_parameters = aggregated.clone();
272 self.current_round += 1;
273
274 Ok(aggregated)
275 }
276
277 pub fn set_client_weights(&mut self, weights: HashMap<String, f32>) {
279 self.client_weights = weights;
280 }
281
282 pub fn get_global_parameters(&self) -> &[Tensor] {
284 &self.global_parameters
285 }
286
287 pub fn get_current_round(&self) -> usize {
289 self.current_round
290 }
291}
292
293#[derive(Debug)]
298pub struct FedProx {
299 fedavg: FedAvg,
300 config: FedProxConfig,
301}
302
303impl FedProx {
304 pub fn new(config: FedProxConfig) -> Self {
306 Self {
307 fedavg: FedAvg::new(config.fedavg_config.clone()),
308 config,
309 }
310 }
311
312 pub fn compute_proximal_term(
314 &self,
315 client_params: &[Tensor],
316 global_params: &[Tensor],
317 ) -> Result<f32> {
318 if client_params.len() != global_params.len() {
319 return Err(anyhow!("Parameter count mismatch"));
320 }
321
322 let mut proximal_loss = 0.0;
323 for (client_param, global_param) in client_params.iter().zip(global_params.iter()) {
324 let diff = client_param.sub(global_param)?;
325 let norm_sq = diff.norm_squared()?.to_scalar()?;
326 proximal_loss += norm_sq;
327 }
328
329 Ok(self.config.mu * proximal_loss / 2.0)
330 }
331
332 pub fn apply_proximal_update(
334 &self,
335 client_params: &mut [Tensor],
336 global_params: &[Tensor],
337 learning_rate: f32,
338 ) -> Result<()> {
339 for (client_param, global_param) in client_params.iter_mut().zip(global_params.iter()) {
340 let diff = client_param.sub(global_param)?;
341 let proximal_grad = diff.mul_scalar(self.config.mu)?;
342 let update = proximal_grad.mul_scalar(learning_rate)?;
343 *client_param = client_param.sub(&update)?;
344 }
345 Ok(())
346 }
347
348 pub fn select_clients(
350 &mut self,
351 available_clients: &[ClientInfo],
352 strategy: ClientSelectionStrategy,
353 ) -> Result<Vec<String>> {
354 self.fedavg.select_clients(available_clients, strategy)
355 }
356
357 pub fn aggregate_updates(
358 &mut self,
359 client_updates: HashMap<String, Vec<Tensor>>,
360 ) -> Result<Vec<Tensor>> {
361 self.fedavg.aggregate_updates(client_updates)
362 }
363
364 pub fn get_global_parameters(&self) -> &[Tensor] {
365 self.fedavg.get_global_parameters()
366 }
367
368 pub fn get_current_round(&self) -> usize {
369 self.fedavg.get_current_round()
370 }
371}
372
373pub struct DifferentialPrivacy {
375 config: DifferentialPrivacyConfig,
376 rng: StdRng,
377}
378
379impl DifferentialPrivacy {
380 pub fn new(config: DifferentialPrivacyConfig) -> Self {
382 Self {
383 config,
384 rng: StdRng::seed_from_u64(42),
385 }
386 }
387
388 pub fn add_noise(&mut self, parameters: &mut [Tensor]) -> Result<()> {
390 let noise_scale = self.compute_noise_scale()?;
391
392 for param in parameters.iter_mut() {
393 let noise = self.generate_noise_tensor(param, noise_scale)?;
394 *param = param.add(&noise)?;
395 }
396
397 Ok(())
398 }
399
400 fn compute_noise_scale(&self) -> Result<f32> {
401 match self.config.noise_mechanism {
402 NoiseMechanism::Gaussian => {
403 let ln_term = (1.25 / self.config.delta).ln();
405 let sigma = (2.0 * ln_term).sqrt() * self.config.sensitivity / self.config.epsilon;
406 Ok(sigma)
407 },
408 NoiseMechanism::Laplace => {
409 Ok(self.config.sensitivity / self.config.epsilon)
411 },
412 }
413 }
414
415 fn generate_noise_tensor(&mut self, reference: &Tensor, scale: f32) -> Result<Tensor> {
416 let shape = reference.shape();
417 let mut noise_data = Vec::new();
418
419 match self.config.noise_mechanism {
420 NoiseMechanism::Gaussian => {
421 use scirs2_core::random::{Distribution, Normal}; let normal = Normal::new(0.0, scale)
423 .map_err(|e| anyhow!("Normal distribution error: {}", e))?;
424
425 for _ in 0..shape.iter().product::<usize>() {
426 noise_data.push(normal.sample(&mut self.rng));
427 }
428 },
429 NoiseMechanism::Laplace => {
430 use scirs2_core::random::{Distribution, Exp}; let exp_dist = Exp::new(1.0 / scale)
434 .map_err(|e| anyhow!("Exponential distribution error: {}", e))?;
435
436 for _ in 0..shape.iter().product::<usize>() {
437 let sign = if self.rng.random::<bool>() { 1.0 } else { -1.0 };
438 let exp_sample = exp_dist.sample(&mut self.rng);
439 noise_data.push(sign * exp_sample);
440 }
441 },
442 }
443
444 Ok(Tensor::from_data(noise_data, &shape.to_vec())?)
445 }
446}
447
448pub struct SecureAggregation {
453 threshold: usize,
454 total_clients: usize,
455}
456
457impl SecureAggregation {
458 pub fn new(threshold: usize, total_clients: usize) -> Result<Self> {
460 if threshold > total_clients {
461 return Err(anyhow!("Threshold cannot exceed total clients"));
462 }
463
464 Ok(Self {
465 threshold,
466 total_clients,
467 })
468 }
469
470 pub fn generate_masks(&self, client_id: &str, round: usize) -> Result<Vec<Tensor>> {
473 let mut rng = StdRng::from_seed({
476 let mut seed = [0u8; 32];
477 let client_hash = format!("{}-{}", client_id, round);
478 let bytes = client_hash.as_bytes();
479 for (i, &byte) in bytes.iter().enumerate().take(32) {
480 seed[i] = byte;
481 }
482 seed
483 });
484
485 let mut masks = Vec::new();
488
489 let parameter_shapes = vec![
492 vec![100, 50], vec![50], vec![50, 20], vec![20], ];
497
498 for shape in parameter_shapes {
499 let mask_size = shape.iter().product::<usize>();
501 let mut mask_data: Vec<f32> = Vec::with_capacity(mask_size);
502
503 for _ in 0..mask_size {
504 mask_data.push(rng.random_range(-1.0..1.0));
506 }
507
508 let mask = Tensor::from_data(mask_data, &shape)?;
509 masks.push(mask);
510 }
511
512 Ok(masks)
513 }
514
515 pub fn secure_aggregate(
517 &self,
518 masked_updates: HashMap<String, Vec<Tensor>>,
519 ) -> Result<Vec<Tensor>> {
520 if masked_updates.len() < self.threshold {
521 return Err(anyhow!("Not enough clients for secure aggregation"));
522 }
523
524 let mut result = Vec::new();
532 let client_count = masked_updates.len() as f32;
533
534 let parameter_count =
536 masked_updates.values().next().map(|update| update.len()).unwrap_or(0);
537
538 for (client_id, update) in &masked_updates {
539 if update.len() != parameter_count {
540 return Err(anyhow!(
541 "Client {} has {} parameters, expected {}",
542 client_id,
543 update.len(),
544 parameter_count
545 ));
546 }
547 }
548
549 for param_idx in 0..parameter_count {
551 let mut parameter_updates = Vec::new();
553 let mut expected_shape: Option<Vec<usize>> = None;
554
555 for (client_id, update) in &masked_updates {
556 let param_update = &update[param_idx];
557
558 if let Some(ref shape) = expected_shape {
560 if param_update.shape() != *shape {
561 return Err(anyhow!(
562 "Client {} parameter {} has shape {:?}, expected {:?}",
563 client_id,
564 param_idx,
565 param_update.shape(),
566 shape
567 ));
568 }
569 } else {
570 expected_shape = Some(param_update.shape());
571 }
572
573 parameter_updates.push(param_update);
574 }
575
576 let shape = expected_shape
578 .ok_or_else(|| anyhow!("No client updates found for parameter {}", param_idx))?;
579 let mut aggregated_param = Tensor::zeros(&shape)?;
580 for param_update in parameter_updates {
581 aggregated_param = aggregated_param.add(param_update)?;
582 }
583
584 result.push(aggregated_param.div_scalar(client_count)?);
588 }
589
590 Ok(result)
591 }
592}
593
594#[cfg(test)]
595mod tests {
596 use super::*;
597
598 #[test]
599 fn test_fedavg_config_default() {
600 let config = FedAvgConfig::default();
601 assert_eq!(config.local_epochs, 5);
602 assert_eq!(config.client_fraction, 0.1);
603 assert_eq!(config.min_clients, 2);
604 }
605
606 #[test]
607 fn test_fedprox_config_default() {
608 let config = FedProxConfig::default();
609 assert_eq!(config.mu, 0.01);
610 assert_eq!(config.fedavg_config.local_epochs, 5);
611 }
612
613 #[test]
614 fn test_differential_privacy_config() {
615 let config = DifferentialPrivacyConfig::default();
616 assert_eq!(config.epsilon, 1.0);
617 assert_eq!(config.delta, 1e-5);
618 assert!(matches!(config.noise_mechanism, NoiseMechanism::Gaussian));
619 }
620
621 #[test]
622 fn test_client_selection_strategies() {
623 let clients = vec![
624 ClientInfo {
625 client_id: "client1".to_string(),
626 data_size: 100,
627 compute_capacity: 0.8,
628 communication_quality: 0.9,
629 available: true,
630 },
631 ClientInfo {
632 client_id: "client2".to_string(),
633 data_size: 200,
634 compute_capacity: 0.6,
635 communication_quality: 0.7,
636 available: true,
637 },
638 ];
639
640 let mut fedavg = FedAvg::new(FedAvgConfig::default());
641
642 let selected = fedavg
644 .select_clients(&clients, ClientSelectionStrategy::Random)
645 .expect("Operation failed in test");
646 assert!(!selected.is_empty());
647
648 let selected = fedavg
650 .select_clients(&clients, ClientSelectionStrategy::DataSize)
651 .expect("Operation failed in test");
652 assert!(!selected.is_empty());
653 }
654
655 #[test]
656 fn test_secure_aggregation_creation() {
657 let secure_agg = SecureAggregation::new(3, 5).expect("Construction failed");
658 assert_eq!(secure_agg.threshold, 3);
659 assert_eq!(secure_agg.total_clients, 5);
660
661 assert!(SecureAggregation::new(6, 5).is_err());
663 }
664}