diff --git a/math_explorer_gui/src/tabs/clinical_trials/mod.rs b/math_explorer_gui/src/tabs/clinical_trials/mod.rs index c6c52b90..022df4db 100644 --- a/math_explorer_gui/src/tabs/clinical_trials/mod.rs +++ b/math_explorer_gui/src/tabs/clinical_trials/mod.rs @@ -1,9 +1,11 @@ use crate::tabs::ExplorerTab; use eframe::egui; +pub mod randomization; pub mod sample_size; pub mod survival; +use randomization::RandomizationTool; use sample_size::SampleSizeCalculatorTool; use survival::SurvivalAnalysisTool; @@ -27,6 +29,7 @@ impl Default for ClinicalTrialsTab { tools: vec![ Box::new(SurvivalAnalysisTool::default()), Box::new(SampleSizeCalculatorTool::default()), + Box::new(RandomizationTool::default()), ], selected_tool_index: 0, } diff --git a/math_explorer_gui/src/tabs/clinical_trials/randomization.rs b/math_explorer_gui/src/tabs/clinical_trials/randomization.rs new file mode 100644 index 00000000..2e1930c2 --- /dev/null +++ b/math_explorer_gui/src/tabs/clinical_trials/randomization.rs @@ -0,0 +1,132 @@ +use crate::tabs::clinical_trials::ClinicalTrialsTool; +use eframe::egui; +use math_explorer::applied::clinical_trials::design::{ + AllocationStrategy, BlockRandomizer, Group, SimpleRandomizer, +}; +use rand::thread_rng; + +#[derive(PartialEq)] +enum RandomizationType { + Simple, + Block, +} + +pub struct RandomizationTool { + randomization_type: RandomizationType, + n_subjects: usize, + block_size: usize, + assignments: Vec, + error_message: Option, +} + +impl Default for RandomizationTool { + fn default() -> Self { + Self { + randomization_type: RandomizationType::Simple, + n_subjects: 10, + block_size: 4, + assignments: Vec::new(), + error_message: None, + } + } +} + +impl ClinicalTrialsTool for RandomizationTool { + fn name(&self) -> &'static str { + "Randomization" + } + + fn show(&mut self, ctx: &egui::Context) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("Interactive Subject Allocation"); + + ui.add_space(10.0); + + ui.horizontal(|ui| { + ui.label("Randomization Type:"); + ui.radio_value( + &mut self.randomization_type, + RandomizationType::Simple, + "Simple", + ); + ui.radio_value( + &mut self.randomization_type, + RandomizationType::Block, + "Block", + ); + }); + + ui.add_space(5.0); + + ui.horizontal(|ui| { + ui.label("Number of Subjects:"); + ui.add(egui::DragValue::new(&mut self.n_subjects).range(1..=1000)); + }); + + if self.randomization_type == RandomizationType::Block { + ui.horizontal(|ui| { + ui.label("Block Size:"); + ui.add(egui::DragValue::new(&mut self.block_size).range(2..=100)); + }); + } + + ui.add_space(10.0); + + if ui.button("Allocate Subjects").clicked() { + self.error_message = None; + self.assignments.clear(); + + let mut rng = thread_rng(); + + match self.randomization_type { + RandomizationType::Simple => { + let randomizer = SimpleRandomizer; + match randomizer.assign(&mut rng, self.n_subjects) { + Ok(assignments) => self.assignments = assignments, + Err(e) => self.error_message = Some(e.to_string()), + } + } + RandomizationType::Block => match BlockRandomizer::new(self.block_size) { + Ok(randomizer) => match randomizer.assign(&mut rng, self.n_subjects) { + Ok(assignments) => self.assignments = assignments, + Err(e) => self.error_message = Some(e.to_string()), + }, + Err(e) => self.error_message = Some(e.to_string()), + }, + } + } + + ui.add_space(10.0); + + if let Some(ref err) = self.error_message { + ui.colored_label(egui::Color32::RED, format!("Error: {}", err)); + } else if !self.assignments.is_empty() { + ui.heading("Assignments:"); + + let mut treatment_count = 0; + let mut control_count = 0; + for group in &self.assignments { + match group { + Group::Treatment => treatment_count += 1, + Group::Control => control_count += 1, + } + } + + ui.label(format!("Total Treatment: {}", treatment_count)); + ui.label(format!("Total Control: {}", control_count)); + + egui::ScrollArea::vertical() + .max_height(300.0) + .show(ui, |ui| { + for (i, group) in self.assignments.iter().enumerate() { + let group_str = match group { + Group::Treatment => "Treatment", + Group::Control => "Control", + }; + ui.label(format!("Subject {}: {}", i + 1, group_str)); + } + }); + } + }); + } +} diff --git a/todo_gui.md b/todo_gui.md index 4a0fff5a..0b3fb42d 100644 --- a/todo_gui.md +++ b/todo_gui.md @@ -109,12 +109,12 @@ This document outlines the roadmap for integrating the various modules of the `m * [x] **Capacity Fade:** Plot capacity vs. cycle number based on depth of discharge (DoD) and temperature. * [ ] **Lifetime Estimator:** Calculator for expected battery life under specific usage profiles. -### 4.2 Clinical Trials +### 4.2 Clinical Trials - **[Implemented]** * **Module:** `applied::clinical_trials` * **Features:** * [x] **Survival Curves:** Kaplan-Meier plot generator. * [x] **Sample Size Calculator:** Form inputs for $\alpha$, $\beta$, and effect size. - * [ ] **Randomization:** Interactive subject allocation tool. + * [x] **Randomization:** Interactive subject allocation tool. ### 4.3 Favoritism (Satirical) * **Module:** `applied::favoritism`