1use scirs2_core::ndarray::{s, Array1, Array2, Array3};
8use scirs2_core::random::prelude::*;
9use scirs2_core::random::rngs::StdRng;
10use scirs2_core::random::{Normal, RngExt};
11use sklears_core::error::{Result, SklearsError};
12use std::f64::consts::PI;
13
14type MultiAgentResult = (Array3<usize>, Array3<usize>, Array2<f64>, Array1<f64>);
16type CommCostResult = (Array2<f64>, Array2<f64>, Array1<f64>, Array1<f64>);
18type SensorFusionResult = (Vec<Array2<f64>>, Array1<f64>, Array1<usize>);
20type MultimodalAlignResult = (Vec<Array2<f64>>, Array2<f64>, Array1<f64>);
22type CrossModalRetrievalResult = (Array2<f64>, Array2<f64>, Array1<usize>, Array1<f64>);
24
25#[derive(Debug, Clone)]
27pub struct MultiAgentConfig {
28 pub n_agents: usize,
29 pub n_states: usize,
30 pub n_actions: usize,
31 pub cooperation_level: f64,
32 pub communication_enabled: bool,
33 pub reward_sharing: bool,
34}
35
36pub fn make_multi_agent_environment(
38 config: MultiAgentConfig,
39 n_episodes: usize,
40 episode_length: usize,
41 random_state: Option<u64>,
42) -> Result<MultiAgentResult> {
43 if config.n_agents == 0 || config.n_states == 0 || config.n_actions == 0 {
44 return Err(SklearsError::InvalidInput(
45 "n_agents, n_states, and n_actions must be positive".to_string(),
46 ));
47 }
48
49 if config.cooperation_level < 0.0 || config.cooperation_level > 1.0 {
50 return Err(SklearsError::InvalidInput(
51 "cooperation_level must be in [0, 1]".to_string(),
52 ));
53 }
54
55 let mut rng = if let Some(seed) = random_state {
56 StdRng::seed_from_u64(seed)
57 } else {
58 StdRng::from_rng(&mut scirs2_core::random::thread_rng())
59 };
60
61 let mut states = Array3::zeros((n_episodes, episode_length, config.n_agents));
63 let mut actions = Array3::zeros((n_episodes, episode_length, config.n_agents));
65 let mut rewards = Array2::zeros((n_episodes, episode_length));
67 let mut episode_rewards = Array1::zeros(n_episodes);
69
70 for episode in 0..n_episodes {
71 let mut episode_reward = 0.0;
72
73 for timestep in 0..episode_length {
74 let mut timestep_reward = 0.0;
75
76 for agent in 0..config.n_agents {
78 let state = if timestep == 0 {
80 rng.random_range(0..config.n_states)
81 } else {
82 if config.cooperation_level > 0.0
84 && rng.random::<f64>() < config.cooperation_level
85 {
86 let other_agent = rng.random_range(0..config.n_agents);
87 if other_agent != agent {
88 let other_state = states[[episode, timestep - 1, other_agent]];
89 (other_state + rng.random_range(0..3)) % config.n_states
90 } else {
91 rng.random_range(0..config.n_states)
92 }
93 } else {
94 rng.random_range(0..config.n_states)
95 }
96 };
97
98 states[[episode, timestep, agent]] = state;
99
100 let action = if config.communication_enabled && rng.random::<f64>() < 0.3 {
102 if timestep > 0 {
104 let other_agent = rng.random_range(0..config.n_agents);
105 let other_action = actions[[episode, timestep - 1, other_agent]];
106 (other_action + rng.random_range(0..2)) % config.n_actions
107 } else {
108 rng.random_range(0..config.n_actions)
109 }
110 } else {
111 rng.random_range(0..config.n_actions)
112 };
113
114 actions[[episode, timestep, agent]] = action;
115
116 let individual_reward = if state == action % config.n_states {
118 1.0
119 } else {
120 0.1 * rng.random::<f64>()
121 };
122
123 if config.reward_sharing {
124 timestep_reward += individual_reward / config.n_agents as f64;
125 } else {
126 timestep_reward += individual_reward;
127 }
128 }
129
130 if config.cooperation_level > 0.0 {
132 let mut coordination_bonus = 0.0;
133 for agent1 in 0..config.n_agents {
134 for agent2 in (agent1 + 1)..config.n_agents {
135 let state_diff = (states[[episode, timestep, agent1]] as i32
136 - states[[episode, timestep, agent2]] as i32)
137 .abs();
138 if state_diff <= 1 {
139 coordination_bonus += 0.5 * config.cooperation_level;
140 }
141 }
142 }
143 timestep_reward += coordination_bonus;
144 }
145
146 rewards[[episode, timestep]] = timestep_reward;
147 episode_reward += timestep_reward;
148 }
149
150 episode_rewards[episode] = episode_reward;
151 }
152
153 Ok((states, actions, rewards, episode_rewards))
154}
155
156pub fn make_vision_language_dataset(
158 n_samples: usize,
159 image_size: (usize, usize),
160 vocab_size: usize,
161 max_sequence_length: usize,
162 alignment_strength: f64,
163 random_state: Option<u64>,
164) -> Result<(Array3<f64>, Array2<usize>, Array1<f64>)> {
165 if n_samples == 0 || vocab_size == 0 || max_sequence_length == 0 {
166 return Err(SklearsError::InvalidInput(
167 "n_samples, vocab_size, and max_sequence_length must be positive".to_string(),
168 ));
169 }
170
171 if !(0.0..=1.0).contains(&alignment_strength) {
172 return Err(SklearsError::InvalidInput(
173 "alignment_strength must be in [0, 1]".to_string(),
174 ));
175 }
176
177 let mut rng = if let Some(seed) = random_state {
178 StdRng::seed_from_u64(seed)
179 } else {
180 StdRng::from_rng(&mut scirs2_core::random::thread_rng())
181 };
182
183 let (height, width) = image_size;
184
185 let mut images = Array3::zeros((n_samples, height, width));
187 let normal = Normal::new(0.5, 0.3).expect("operation should succeed");
188
189 for i in 0..n_samples {
190 for h in 0..height {
191 for w in 0..width {
192 let sample: f64 = rng.sample(normal);
193 images[[i, h, w]] = sample.clamp(0.0, 1.0);
194 }
195 }
196 }
197
198 let mut texts = Array2::zeros((n_samples, max_sequence_length));
200 let mut alignment_scores = Array1::zeros(n_samples);
201
202 for i in 0..n_samples {
203 let image_mean = images.slice(s![i, .., ..]).mean().unwrap_or(0.5);
205
206 for j in 0..max_sequence_length {
208 let token = if rng.random::<f64>() < alignment_strength {
209 ((image_mean * vocab_size as f64) as usize).min(vocab_size - 1)
212 } else {
213 rng.random_range(0..vocab_size)
215 };
216 texts[[i, j]] = token;
217 }
218
219 let text_diversity = texts
221 .slice(s![i, ..])
222 .iter()
223 .map(|&t| t as f64 / vocab_size as f64)
224 .collect::<Vec<_>>();
225 let text_mean = text_diversity.iter().sum::<f64>() / max_sequence_length as f64;
226 alignment_scores[i] = 1.0 - (image_mean - text_mean).abs();
227 }
228
229 Ok((images, texts, alignment_scores))
230}
231
232pub fn make_audio_visual_dataset(
234 n_samples: usize,
235 audio_length: usize,
236 video_frames: usize,
237 frame_size: (usize, usize),
238 sync_strength: f64,
239 random_state: Option<u64>,
240) -> Result<(Array2<f64>, Array2<f64>, Array1<f64>)> {
241 if n_samples == 0 || audio_length == 0 || video_frames == 0 {
242 return Err(SklearsError::InvalidInput(
243 "n_samples, audio_length, and video_frames must be positive".to_string(),
244 ));
245 }
246
247 if !(0.0..=1.0).contains(&sync_strength) {
248 return Err(SklearsError::InvalidInput(
249 "sync_strength must be in [0, 1]".to_string(),
250 ));
251 }
252
253 let mut rng = if let Some(seed) = random_state {
254 StdRng::seed_from_u64(seed)
255 } else {
256 StdRng::from_rng(&mut scirs2_core::random::thread_rng())
257 };
258
259 let (height, width) = frame_size;
260
261 let mut audio = Array2::zeros((n_samples, audio_length));
263 let normal = Normal::new(0.0, 0.5).expect("operation should succeed");
264
265 for i in 0..n_samples {
266 let base_frequency = rng.random_range(0.1..0.5);
267 for t in 0..audio_length {
268 let time = t as f64 / audio_length as f64;
269 let signal = (2.0 * PI * base_frequency * time).sin();
270 let noise: f64 = rng.sample(normal);
271 audio[[i, t]] = signal + 0.1 * noise;
272 }
273 }
274
275 let mut video = Array2::zeros((n_samples, video_frames * height * width));
277 let mut sync_scores = Array1::zeros(n_samples);
278
279 for i in 0..n_samples {
280 let audio_energy = audio.slice(s![i, ..]).mapv(|x| x * x).mean().unwrap_or(0.0);
281
282 for frame in 0..video_frames {
283 let frame_start = frame * height * width;
284 let audio_frame_idx = (frame * audio_length) / video_frames;
285 let audio_frame_energy = if audio_frame_idx < audio_length {
286 audio[[i, audio_frame_idx]].abs()
287 } else {
288 0.0
289 };
290
291 for pixel in 0..(height * width) {
292 let idx = frame_start + pixel;
293 if rng.random::<f64>() < sync_strength {
294 video[[i, idx]] = audio_frame_energy + 0.2 * rng.random::<f64>();
296 } else {
297 video[[i, idx]] = rng.random::<f64>();
299 }
300 }
301 }
302
303 let video_energy = video.slice(s![i, ..]).mapv(|x| x * x).mean().unwrap_or(0.0);
305 sync_scores[i] = 1.0 - (audio_energy.sqrt() - video_energy.sqrt()).abs();
306 }
307
308 Ok((audio, video, sync_scores))
309}
310
311#[derive(Debug, Clone)]
313pub struct CommunicationCostConfig {
314 pub n_clients: usize,
315 pub network_topology: String, pub bandwidth_mbps: f64,
317 pub latency_ms: f64,
318 pub packet_loss_rate: f64,
319 pub compression_ratio: f64,
320}
321
322pub fn make_communication_cost_datasets(
324 config: CommunicationCostConfig,
325 n_rounds: usize,
326 model_size_mb: f64,
327 random_state: Option<u64>,
328) -> Result<CommCostResult> {
329 if config.n_clients == 0 || n_rounds == 0 {
330 return Err(SklearsError::InvalidInput(
331 "n_clients and n_rounds must be positive".to_string(),
332 ));
333 }
334
335 if model_size_mb <= 0.0 || config.bandwidth_mbps <= 0.0 || config.latency_ms < 0.0 {
336 return Err(SklearsError::InvalidInput(
337 "model_size_mb, bandwidth_mbps must be positive, latency_ms must be non-negative"
338 .to_string(),
339 ));
340 }
341
342 if config.packet_loss_rate < 0.0 || config.packet_loss_rate > 1.0 {
343 return Err(SklearsError::InvalidInput(
344 "packet_loss_rate must be in [0, 1]".to_string(),
345 ));
346 }
347
348 if config.compression_ratio <= 0.0 || config.compression_ratio > 1.0 {
349 return Err(SklearsError::InvalidInput(
350 "compression_ratio must be in (0, 1]".to_string(),
351 ));
352 }
353
354 let mut rng = if let Some(seed) = random_state {
355 StdRng::seed_from_u64(seed)
356 } else {
357 StdRng::from_rng(&mut scirs2_core::random::thread_rng())
358 };
359
360 let mut upload_costs = Array2::zeros((n_rounds, config.n_clients));
362 let mut download_costs = Array2::zeros((n_rounds, config.n_clients));
363 let mut round_times = Array1::zeros(n_rounds);
364 let mut total_bandwidth_usage = Array1::zeros(n_rounds);
365
366 let (_n_connections, aggregation_factor) = match config.network_topology.as_str() {
368 "star" => (1, 1.0), "ring" => (2, 0.5), "full_mesh" => (config.n_clients - 1, 1.0 / (config.n_clients - 1) as f64),
371 "hierarchical" => ((config.n_clients as f64).sqrt() as usize, 0.3),
372 _ => {
373 return Err(SklearsError::InvalidInput(
374 "Invalid network topology. Must be one of: star, ring, full_mesh, hierarchical"
375 .to_string(),
376 ))
377 }
378 };
379
380 let compressed_model_size = model_size_mb * config.compression_ratio;
381
382 for round in 0..n_rounds {
383 let mut max_round_time: f64 = 0.0;
384 let mut total_bandwidth_round = 0.0;
385
386 for client in 0..config.n_clients {
387 let bandwidth_variation = 1.0 + 0.3 * (rng.random::<f64>() - 0.5); let latency_variation = 1.0 + 0.5 * (rng.random::<f64>() - 0.5); let effective_bandwidth = config.bandwidth_mbps * bandwidth_variation;
391 let effective_latency = config.latency_ms * latency_variation;
392
393 let upload_time = (compressed_model_size / effective_bandwidth) * 1000.0; let upload_total_time = upload_time + effective_latency;
396
397 let retransmission_factor = if config.packet_loss_rate > 0.0 {
399 1.0 / (1.0 - config.packet_loss_rate).max(0.1) } else {
401 1.0
402 };
403
404 let upload_cost = upload_total_time * retransmission_factor;
405 upload_costs[[round, client]] = upload_cost;
406
407 let download_time = (compressed_model_size / effective_bandwidth) * 1000.0; let download_total_time = download_time + effective_latency;
410 let download_cost = download_total_time * retransmission_factor * aggregation_factor;
411 download_costs[[round, client]] = download_cost;
412
413 let client_round_time = upload_cost + download_cost;
415 max_round_time = max_round_time.max(client_round_time);
416
417 let bandwidth_used = (compressed_model_size * 2.0) * retransmission_factor; total_bandwidth_round += bandwidth_used;
420 }
421
422 round_times[round] = max_round_time;
423 total_bandwidth_usage[round] = total_bandwidth_round;
424 }
425
426 Ok((
427 upload_costs,
428 download_costs,
429 round_times,
430 total_bandwidth_usage,
431 ))
432}
433
434pub fn make_sensor_fusion_dataset(
436 n_samples: usize,
437 sensor_types: Vec<String>, temporal_length: usize,
439 sync_accuracy: f64, random_state: Option<u64>,
441) -> Result<SensorFusionResult> {
442 if n_samples == 0 || sensor_types.is_empty() || temporal_length == 0 {
443 return Err(SklearsError::InvalidInput(
444 "n_samples, sensor_types, and temporal_length must be positive".to_string(),
445 ));
446 }
447
448 if !(0.0..=1.0).contains(&sync_accuracy) {
449 return Err(SklearsError::InvalidInput(
450 "sync_accuracy must be in [0, 1]".to_string(),
451 ));
452 }
453
454 let mut rng = if let Some(seed) = random_state {
455 StdRng::seed_from_u64(seed)
456 } else {
457 StdRng::from_rng(&mut scirs2_core::random::thread_rng())
458 };
459
460 let n_sensors = sensor_types.len();
461 let mut sensor_data = Vec::new();
462 let mut fusion_quality = Array1::zeros(n_samples);
463 let mut ground_truth_events = Array1::zeros(n_samples);
464
465 let sensor_dimensions: Vec<usize> = sensor_types
467 .iter()
468 .map(|sensor_type| {
469 match sensor_type.as_str() {
470 "accelerometer" => 3, "gyroscope" => 3, "magnetometer" => 3, "camera" => 16, "lidar" => 8, "gps" => 2, "microphone" => 1, "pressure" => 1, _ => 4, }
480 })
481 .collect();
482
483 for &dim in &sensor_dimensions {
485 sensor_data.push(Array2::zeros((n_samples, temporal_length * dim)));
486 }
487
488 let normal = Normal::new(0.0, 1.0).expect("operation should succeed");
489
490 for sample in 0..n_samples {
491 let event_type = rng.random_range(0..5); ground_truth_events[sample] = event_type;
494
495 let base_frequency = 0.1 + (event_type as f64) * 0.05;
497 let base_amplitude = 0.5 + (event_type as f64) * 0.2;
498
499 let mut sensor_correlations = Vec::new();
500
501 for (sensor_idx, sensor_type) in sensor_types.iter().enumerate() {
502 let dims = sensor_dimensions[sensor_idx];
503 let mut sensor_correlation = 0.0;
504
505 for t in 0..temporal_length {
506 let time = t as f64 / temporal_length as f64;
507
508 let base_signal = base_amplitude * (2.0 * PI * base_frequency * time).sin();
510
511 let sync_error = if rng.random::<f64>() > sync_accuracy {
513 0.1 * rng.random::<f64>() } else {
515 0.0
516 };
517
518 for dim in 0..dims {
519 let feature_idx = t * dims + dim;
520
521 let sensor_signal = match sensor_type.as_str() {
523 "accelerometer" => {
524 let gravity_component = if dim == 2 { 9.81 } else { 0.0 };
525 base_signal * (1.0 + dim as f64 * 0.3) + gravity_component
526 }
527 "gyroscope" => {
528 base_signal * (2.0 + dim as f64 * 0.5) }
530 "magnetometer" => {
531 let magnetic_field = if dim == 0 { 25.0 } else { 5.0 };
532 base_signal * 0.5 + magnetic_field
533 }
534 "camera" => {
535 let pixel_intensity = 0.5 + 0.3 * base_signal;
536 pixel_intensity.clamp(0.0, 1.0)
537 }
538 "lidar" => {
539 let distance = 5.0 + 2.0 * base_signal + 0.5 * (dim as f64);
540 distance.max(0.1)
541 }
542 "gps" => {
543 let coord_variation = 0.0001 * base_signal;
544 if dim == 0 {
545 37.7749 + coord_variation
546 } else {
547 -122.4194 + coord_variation
548 }
549 }
550 "microphone" => base_signal * 0.8,
551 "pressure" => {
552 1013.25 + 10.0 * base_signal }
554 _ => base_signal,
555 };
556
557 let noise: f64 = rng.sample(normal);
559 let final_signal = sensor_signal + 0.1 * noise + sync_error;
560
561 sensor_data[sensor_idx][[sample, feature_idx]] = final_signal;
562
563 sensor_correlation += (final_signal - sensor_signal).abs();
565 }
566 }
567
568 sensor_correlations.push(sensor_correlation / (temporal_length * dims) as f64);
569 }
570
571 let avg_correlation = sensor_correlations.iter().sum::<f64>() / n_sensors as f64;
573 fusion_quality[sample] = (1.0 - avg_correlation.min(1.0)).max(0.0);
574 }
575
576 Ok((sensor_data, fusion_quality, ground_truth_events))
577}
578
579pub fn make_multimodal_alignment_dataset(
581 n_samples: usize,
582 modality_types: Vec<String>, alignment_strength: f64,
584 cross_modal_noise: f64,
585 random_state: Option<u64>,
586) -> Result<MultimodalAlignResult> {
587 if n_samples == 0 || modality_types.is_empty() {
588 return Err(SklearsError::InvalidInput(
589 "n_samples and modality_types must be positive".to_string(),
590 ));
591 }
592
593 if !(0.0..=1.0).contains(&alignment_strength) {
594 return Err(SklearsError::InvalidInput(
595 "alignment_strength must be in [0, 1]".to_string(),
596 ));
597 }
598
599 if !(0.0..=1.0).contains(&cross_modal_noise) {
600 return Err(SklearsError::InvalidInput(
601 "cross_modal_noise must be in [0, 1]".to_string(),
602 ));
603 }
604
605 let mut rng = if let Some(seed) = random_state {
606 StdRng::seed_from_u64(seed)
607 } else {
608 StdRng::from_rng(&mut scirs2_core::random::thread_rng())
609 };
610
611 let n_modalities = modality_types.len();
612 let mut modality_data = Vec::new();
613 let mut alignment_scores = Array1::zeros(n_samples);
614
615 let modality_dimensions: Vec<usize> = modality_types
617 .iter()
618 .map(|modality| {
619 match modality.as_str() {
620 "text" => 300, "image" => 2048, "audio" => 128, "video" => 1024, _ => 256, }
626 })
627 .collect();
628
629 for &dim in &modality_dimensions {
631 modality_data.push(Array2::zeros((n_samples, dim)));
632 }
633
634 let mut cross_modal_alignment = Array2::zeros((n_samples, n_modalities * n_modalities));
636
637 let normal = Normal::new(0.0, 1.0).expect("operation should succeed");
638
639 for sample in 0..n_samples {
640 let mut shared_content = Array1::zeros(64); for i in 0..64 {
643 shared_content[i] = rng.sample(normal);
644 }
645
646 let mut modality_embeddings = Vec::new();
647
648 for (mod_idx, modality) in modality_types.iter().enumerate() {
649 let dims = modality_dimensions[mod_idx];
650 let mut embedding = Array1::zeros(dims);
651
652 for i in 0..dims {
654 let shared_influence = if i < 64 {
655 alignment_strength * shared_content[i]
656 } else {
657 0.0
658 };
659
660 let modality_specific = match modality.as_str() {
661 "text" => {
662 let word_freq = rng.random::<f64>() * 0.1;
664 let semantic_sim = shared_influence * 0.8;
665 semantic_sim + word_freq
666 }
667 "image" => {
668 let visual_pattern = (shared_influence * 2.0).sin() * 0.5;
670 let color_intensity = rng.random::<f64>() * 0.3;
671 visual_pattern + color_intensity
672 }
673 "audio" => {
674 let spectral_content = shared_influence * 0.6;
676 let rhythm_component = ((i as f64 / 10.0) * PI).sin() * 0.2;
677 spectral_content + rhythm_component
678 }
679 "video" => {
680 let temporal_pattern = shared_influence * 0.7;
682 let motion_component = rng.random::<f64>() * 0.2;
683 temporal_pattern + motion_component
684 }
685 _ => shared_influence,
686 };
687
688 let noise: f64 = rng.sample(normal);
689 embedding[i] = modality_specific + cross_modal_noise * noise;
690 }
691
692 modality_embeddings.push(embedding.clone());
693
694 for i in 0..dims {
696 modality_data[mod_idx][[sample, i]] = embedding[i];
697 }
698 }
699
700 let mut total_alignment = 0.0;
702 let mut alignment_count = 0;
703
704 for i in 0..n_modalities {
705 for j in (i + 1)..n_modalities {
706 let shared_len = 64
709 .min(modality_embeddings[i].len())
710 .min(modality_embeddings[j].len());
711
712 let mut dot_product = 0.0;
713 let mut norm_i_sq = 0.0;
714 let mut norm_j_sq = 0.0;
715
716 #[allow(clippy::needless_range_loop)]
718 for k in 0..shared_len {
719 let val_i = modality_embeddings[i][k];
720 let val_j = modality_embeddings[j][k];
721 dot_product += val_i * val_j;
722 norm_i_sq += val_i * val_i;
723 norm_j_sq += val_j * val_j;
724 }
725
726 let cosine_sim = if norm_i_sq > 0.0 && norm_j_sq > 0.0 {
727 dot_product / (norm_i_sq.sqrt() * norm_j_sq.sqrt())
728 } else {
729 0.0
730 };
731
732 cross_modal_alignment[[sample, i * n_modalities + j]] = cosine_sim;
733 total_alignment += cosine_sim.abs();
734 alignment_count += 1;
735 }
736 }
737
738 alignment_scores[sample] = total_alignment / alignment_count as f64;
739 }
740
741 Ok((modality_data, cross_modal_alignment, alignment_scores))
742}
743
744pub fn make_cross_modal_retrieval_dataset(
746 n_samples: usize,
747 source_modality: String, target_modality: String, n_distractors: usize, retrieval_difficulty: f64, random_state: Option<u64>,
752) -> Result<CrossModalRetrievalResult> {
753 if n_samples == 0 || n_distractors == 0 {
754 return Err(SklearsError::InvalidInput(
755 "n_samples and n_distractors must be positive".to_string(),
756 ));
757 }
758
759 if !(0.0..=1.0).contains(&retrieval_difficulty) {
760 return Err(SklearsError::InvalidInput(
761 "retrieval_difficulty must be in [0, 1]".to_string(),
762 ));
763 }
764
765 let mut rng = if let Some(seed) = random_state {
766 StdRng::seed_from_u64(seed)
767 } else {
768 StdRng::from_rng(&mut scirs2_core::random::thread_rng())
769 };
770
771 let source_dim = match source_modality.as_str() {
773 "text" => 300,
774 "image" => 2048,
775 "audio" => 128,
776 _ => 256,
777 };
778
779 let target_dim = match target_modality.as_str() {
780 "text" => 300,
781 "image" => 2048,
782 "audio" => 128,
783 _ => 256,
784 };
785
786 let total_targets = n_samples * (1 + n_distractors);
787 let mut source_embeddings = Array2::zeros((n_samples, source_dim));
788 let mut target_embeddings = Array2::zeros((total_targets, target_dim));
789 let mut ground_truth_indices = Array1::zeros(n_samples);
790 let mut retrieval_scores = Array1::zeros(n_samples);
791
792 let normal = Normal::new(0.0, 1.0).expect("operation should succeed");
793
794 for sample in 0..n_samples {
795 let mut shared_content = Array1::zeros(64);
797 for i in 0..64 {
798 shared_content[i] = rng.sample(normal);
799 }
800
801 for i in 0..source_dim {
803 let shared_influence = if i < 64 { 0.8 * shared_content[i] } else { 0.0 };
804
805 let modality_specific = match source_modality.as_str() {
806 "text" => {
807 let semantic_weight = 0.6 + 0.4 * rng.random::<f64>();
808 shared_influence * semantic_weight
809 }
810 "image" => (shared_influence * 1.5).sin() * 0.7,
811 "audio" => shared_influence * 0.8,
812 _ => shared_influence,
813 };
814
815 let noise: f64 = rng.sample(normal);
816 source_embeddings[[sample, i]] = modality_specific + 0.1 * noise;
817 }
818
819 let target_idx = sample * (1 + n_distractors);
821 ground_truth_indices[sample] = target_idx;
822
823 for i in 0..target_dim {
824 let shared_influence = if i < 64 { 0.8 * shared_content[i] } else { 0.0 };
825
826 let difficulty_noise = retrieval_difficulty * rng.sample(normal);
827
828 let modality_specific = match target_modality.as_str() {
829 "text" => {
830 let semantic_weight = 0.6 + 0.4 * rng.random::<f64>();
831 shared_influence * semantic_weight
832 }
833 "image" => (shared_influence * 1.5).sin() * 0.7,
834 "audio" => shared_influence * 0.8,
835 _ => shared_influence,
836 };
837
838 let noise: f64 = rng.sample(normal);
839 target_embeddings[[target_idx, i]] = modality_specific + 0.1 * noise + difficulty_noise;
840 }
841
842 for distractor in 0..n_distractors {
844 let distractor_idx = target_idx + 1 + distractor;
845
846 let mut distractor_content = Array1::zeros(64);
848 for i in 0..64 {
849 distractor_content[i] = rng.sample(normal);
850 }
851
852 for i in 0..target_dim {
853 let shared_influence = if i < 64 {
854 0.8 * distractor_content[i]
855 } else {
856 0.0
857 };
858
859 let modality_specific = match target_modality.as_str() {
860 "text" => {
861 let semantic_weight = 0.6 + 0.4 * rng.random::<f64>();
862 shared_influence * semantic_weight
863 }
864 "image" => (shared_influence * 1.5).sin() * 0.7,
865 "audio" => shared_influence * 0.8,
866 _ => shared_influence,
867 };
868
869 let noise: f64 = rng.sample(normal);
870 target_embeddings[[distractor_idx, i]] = modality_specific + 0.1 * noise;
871 }
872 }
873
874 let shared_len = 64.min(source_dim).min(target_dim);
877
878 let mut dot_product = 0.0;
879 let mut source_norm_sq: f64 = 0.0;
880 let mut target_norm_sq: f64 = 0.0;
881
882 for k in 0..shared_len {
883 let source_val = source_embeddings[[sample, k]];
884 let target_val = target_embeddings[[target_idx, k]];
885 dot_product += source_val * target_val;
886 source_norm_sq += source_val * source_val;
887 target_norm_sq += target_val * target_val;
888 }
889
890 let cosine_sim = if source_norm_sq > 0.0 && target_norm_sq > 0.0 {
891 dot_product / (source_norm_sq.sqrt() * target_norm_sq.sqrt())
892 } else {
893 0.0
894 };
895
896 retrieval_scores[sample] = cosine_sim;
897 }
898
899 Ok((
900 source_embeddings,
901 target_embeddings,
902 ground_truth_indices,
903 retrieval_scores,
904 ))
905}
906
907#[allow(non_snake_case)]
908#[cfg(test)]
909mod tests {
910 use super::*;
911
912 #[test]
913 fn test_make_multi_agent_environment() {
914 let config = MultiAgentConfig {
915 n_agents: 3,
916 n_states: 4,
917 n_actions: 2,
918 cooperation_level: 0.5,
919 communication_enabled: true,
920 reward_sharing: false,
921 };
922
923 let (states, actions, rewards, episode_rewards) =
924 make_multi_agent_environment(config, 10, 20, Some(42))
925 .expect("operation should succeed");
926
927 assert_eq!(states.shape(), &[10, 20, 3]);
928 assert_eq!(actions.shape(), &[10, 20, 3]);
929 assert_eq!(rewards.shape(), &[10, 20]);
930 assert_eq!(episode_rewards.len(), 10);
931
932 for &state in states.iter() {
934 assert!(state < 4, "States should be within [0, n_states)");
935 }
936
937 for &action in actions.iter() {
939 assert!(action < 2, "Actions should be within [0, n_actions)");
940 }
941 }
942
943 #[test]
944 fn test_make_vision_language_dataset() {
945 let (images, texts, alignment_scores) =
946 make_vision_language_dataset(50, (32, 32), 1000, 10, 0.7, Some(42))
947 .expect("operation should succeed");
948
949 assert_eq!(images.shape(), &[50, 32, 32]);
950 assert_eq!(texts.shape(), &[50, 10]);
951 assert_eq!(alignment_scores.len(), 50);
952
953 for &pixel in images.iter() {
955 assert!(
956 (0.0..=1.0).contains(&pixel),
957 "Image pixels should be in [0, 1]"
958 );
959 }
960
961 for &token in texts.iter() {
963 assert!(token < 1000, "Text tokens should be within vocab_size");
964 }
965
966 for &score in alignment_scores.iter() {
968 assert!(
969 (0.0..=1.0).contains(&score),
970 "Alignment scores should be in [0, 1]"
971 );
972 }
973 }
974
975 #[test]
976 fn test_make_audio_visual_dataset() {
977 let (audio, video, sync_scores) =
978 make_audio_visual_dataset(20, 100, 10, (8, 8), 0.8, Some(42))
979 .expect("operation should succeed");
980
981 assert_eq!(audio.shape(), &[20, 100]);
982 assert_eq!(video.shape(), &[20, 640]); assert_eq!(sync_scores.len(), 20);
984
985 let audio_var = audio.var(0.0);
987 assert!(audio_var > 0.0, "Audio should have variation");
988
989 for &score in sync_scores.iter() {
991 assert!(
992 (0.0..=1.0).contains(&score),
993 "Sync scores should be in [0, 1]"
994 );
995 }
996 }
997
998 #[test]
999 fn test_make_communication_cost_datasets() {
1000 let config = CommunicationCostConfig {
1001 n_clients: 5,
1002 network_topology: "star".to_string(),
1003 bandwidth_mbps: 10.0,
1004 latency_ms: 50.0,
1005 packet_loss_rate: 0.05,
1006 compression_ratio: 0.8,
1007 };
1008
1009 let (upload_costs, download_costs, round_times, bandwidth_usage) =
1010 make_communication_cost_datasets(config, 10, 2.5, Some(42))
1011 .expect("operation should succeed");
1012
1013 assert_eq!(upload_costs.shape(), &[10, 5]);
1014 assert_eq!(download_costs.shape(), &[10, 5]);
1015 assert_eq!(round_times.len(), 10);
1016 assert_eq!(bandwidth_usage.len(), 10);
1017
1018 for &cost in upload_costs.iter() {
1020 assert!(cost > 0.0, "Upload costs should be positive");
1021 }
1022
1023 for &cost in download_costs.iter() {
1024 assert!(cost > 0.0, "Download costs should be positive");
1025 }
1026
1027 for &time in round_times.iter() {
1028 assert!(time > 0.0, "Round times should be positive");
1029 }
1030
1031 for &usage in bandwidth_usage.iter() {
1032 assert!(usage > 0.0, "Bandwidth usage should be positive");
1033 }
1034 }
1035
1036 #[test]
1037 fn test_make_sensor_fusion_dataset() {
1038 let sensor_types = vec![
1039 "accelerometer".to_string(),
1040 "gyroscope".to_string(),
1041 "camera".to_string(),
1042 ];
1043
1044 let (sensor_data, fusion_quality, ground_truth_events) =
1045 make_sensor_fusion_dataset(20, sensor_types, 10, 0.9, Some(42))
1046 .expect("operation should succeed");
1047
1048 assert_eq!(sensor_data.len(), 3); assert_eq!(sensor_data[0].shape(), &[20, 30]); assert_eq!(sensor_data[1].shape(), &[20, 30]); assert_eq!(sensor_data[2].shape(), &[20, 160]); assert_eq!(fusion_quality.len(), 20);
1053 assert_eq!(ground_truth_events.len(), 20);
1054
1055 for &quality in fusion_quality.iter() {
1057 assert!(
1058 (0.0..=1.0).contains(&quality),
1059 "Fusion quality should be in [0, 1]"
1060 );
1061 }
1062
1063 for &event in ground_truth_events.iter() {
1065 assert!(event < 5, "Ground truth events should be within [0, 5)");
1066 }
1067 }
1068
1069 #[test]
1070 fn test_make_multimodal_alignment_dataset() {
1071 let modality_types = vec!["text".to_string(), "image".to_string(), "audio".to_string()];
1072
1073 let (modality_data, cross_modal_alignment, alignment_scores) =
1074 make_multimodal_alignment_dataset(15, modality_types, 0.8, 0.2, Some(42))
1075 .expect("operation should succeed");
1076
1077 assert_eq!(modality_data.len(), 3); assert_eq!(modality_data[0].shape(), &[15, 300]); assert_eq!(modality_data[1].shape(), &[15, 2048]); assert_eq!(modality_data[2].shape(), &[15, 128]); assert_eq!(cross_modal_alignment.shape(), &[15, 9]); assert_eq!(alignment_scores.len(), 15);
1083
1084 for &score in alignment_scores.iter() {
1086 assert!(
1087 (0.0..=1.0).contains(&score),
1088 "Alignment scores should be in [0, 1]"
1089 );
1090 }
1091 }
1092
1093 #[test]
1094 fn test_make_cross_modal_retrieval_dataset() {
1095 let (source_embeddings, target_embeddings, ground_truth_indices, retrieval_scores) =
1096 make_cross_modal_retrieval_dataset(
1097 10,
1098 "text".to_string(),
1099 "image".to_string(),
1100 5,
1101 0.3,
1102 Some(42),
1103 )
1104 .expect("operation should succeed");
1105
1106 assert_eq!(source_embeddings.shape(), &[10, 300]); assert_eq!(target_embeddings.shape(), &[60, 2048]); assert_eq!(ground_truth_indices.len(), 10);
1109 assert_eq!(retrieval_scores.len(), 10);
1110
1111 for &idx in ground_truth_indices.iter() {
1113 assert!(
1114 idx < 60,
1115 "Ground truth indices should be within target range"
1116 );
1117 }
1118
1119 for &score in retrieval_scores.iter() {
1121 assert!(
1122 (-1.0..=1.0).contains(&score),
1123 "Retrieval scores should be in [-1, 1] (cosine similarity)"
1124 );
1125 }
1126 }
1127}