quantrs2_tytan/solution_debugger/
mod.rs1pub mod analysis;
7pub mod comparison;
8pub mod config;
9pub mod constraint_analyzer;
10pub mod energy_analyzer;
11pub mod reporting;
12pub mod types;
13pub mod visualization;
14
15#[cfg(feature = "dwave")]
16use crate::compile::{Compile, CompiledModel};
17
18pub use analysis::*;
20pub use comparison::*;
21pub use config::*;
22pub use constraint_analyzer::*;
23pub use energy_analyzer::*;
24pub use reporting::*;
25pub use types::*;
26pub use visualization::*;
27
28pub struct SolutionDebugger {
30 config: config::DebuggerConfig,
32 problem_info: types::ProblemInfo,
34 constraint_analyzer: constraint_analyzer::ConstraintAnalyzer,
36 energy_analyzer: energy_analyzer::EnergyAnalyzer,
38 comparator: comparison::SolutionComparator,
40 visualizer: visualization::SolutionVisualizer,
42}
43
44impl SolutionDebugger {
45 pub fn new(problem_info: types::ProblemInfo, config: config::DebuggerConfig) -> Self {
47 Self {
48 config,
49 problem_info,
50 constraint_analyzer: constraint_analyzer::ConstraintAnalyzer::new(1e-6),
51 energy_analyzer: energy_analyzer::EnergyAnalyzer::new(2),
52 comparator: comparison::SolutionComparator::new(),
53 visualizer: visualization::SolutionVisualizer::new(),
54 }
55 }
56
57 pub fn debug_solution(&mut self, solution: &types::Solution) -> reporting::DebugReport {
59 let mut report = reporting::DebugReport {
60 solution: solution.clone(),
61 constraint_analysis: None,
62 energy_analysis: None,
63 comparison_results: Vec::new(),
64 visualizations: Vec::new(),
65 issues: Vec::new(),
66 suggestions: Vec::new(),
67 summary: reporting::DebugSummary::default(),
68 };
69
70 if self.config.check_constraints {
72 report.constraint_analysis = Some(self.analyze_constraints(solution));
73 }
74
75 if self.config.analyze_energy {
77 report.energy_analysis = Some(self.analyze_energy(solution));
78 }
79
80 if self.config.compare_solutions {
82 if let Some(ref optimal) = self.problem_info.optimal_solution {
83 report
84 .comparison_results
85 .push(self.compare_solutions(solution, optimal));
86 }
87 }
88
89 if self.config.generate_visuals {
91 report.visualizations = self.generate_visualizations(solution);
92 }
93
94 report.issues = self.identify_issues(&report);
96
97 report.suggestions = self.generate_suggestions(&report);
99
100 report.summary = self.generate_summary(&report);
102
103 report
104 }
105
106 fn analyze_constraints(&mut self, solution: &types::Solution) -> analysis::ConstraintAnalysis {
108 let violations = self
109 .constraint_analyzer
110 .analyze(&self.problem_info.constraints, &solution.assignments);
111
112 let satisfied_count = self.problem_info.constraints.len() - violations.len();
113 let satisfaction_rate = satisfied_count as f64 / self.problem_info.constraints.len() as f64;
114
115 analysis::ConstraintAnalysis {
116 total_constraints: self.problem_info.constraints.len(),
117 satisfied: satisfied_count,
118 violated: violations.len(),
119 satisfaction_rate,
120 penalty_incurred: violations
121 .iter()
122 .map(|v| v.constraint.penalty * v.violation_amount)
123 .sum(),
124 violations,
125 }
126 }
127
128 fn analyze_energy(&mut self, solution: &types::Solution) -> analysis::EnergyAnalysis {
130 let breakdown = self.energy_analyzer.analyze(
131 &self.problem_info.qubo,
132 &solution.assignments,
133 &self.problem_info.var_map,
134 );
135
136 let mut critical_vars: Vec<_> = breakdown
138 .variable_contributions
139 .iter()
140 .map(|(var, contrib)| (var.clone(), contrib.abs()))
141 .collect();
142 critical_vars.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
143 critical_vars.truncate(10);
144
145 let mut critical_interactions: Vec<_> = breakdown
147 .interaction_contributions
148 .iter()
149 .map(|(vars, contrib)| (vars.clone(), contrib.abs()))
150 .collect();
151 critical_interactions
152 .sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
153 critical_interactions.truncate(10);
154
155 analysis::EnergyAnalysis {
156 total_energy: breakdown.total_energy,
157 breakdown: breakdown.clone(),
158 critical_variables: critical_vars,
159 critical_interactions,
160 improvement_potential: self.estimate_improvement_potential(&breakdown),
161 }
162 }
163
164 fn compare_solutions(
166 &self,
167 sol1: &types::Solution,
168 sol2: &types::Solution,
169 ) -> comparison::ComparisonResult {
170 self.comparator.compare(sol1, sol2, &self.problem_info)
171 }
172
173 fn generate_visualizations(
175 &self,
176 solution: &types::Solution,
177 ) -> Vec<visualization::Visualization> {
178 vec![
179 self.visualizer
181 .visualize_solution_matrix(solution, &self.problem_info),
182 self.visualizer
184 .visualize_energy_landscape(solution, &self.problem_info),
185 self.visualizer
187 .visualize_constraint_graph(solution, &self.problem_info),
188 ]
189 }
190
191 fn identify_issues(&self, report: &reporting::DebugReport) -> Vec<reporting::Issue> {
193 let mut issues = Vec::new();
194
195 if let Some(ref constraint_analysis) = report.constraint_analysis {
197 if constraint_analysis.satisfaction_rate < 0.95 {
198 issues.push(reporting::Issue {
199 severity: reporting::IssueSeverity::High,
200 category: "Constraints".to_string(),
201 description: format!(
202 "Only {:.1}% of constraints satisfied",
203 constraint_analysis.satisfaction_rate * 100.0
204 ),
205 location: "Constraint analysis".to_string(),
206 suggested_action: "Review constraint violations and adjust solution"
207 .to_string(),
208 });
209 }
210 }
211
212 if let Some(ref energy_analysis) = report.energy_analysis {
214 if energy_analysis.improvement_potential > 0.1 {
215 issues.push(reporting::Issue {
216 severity: reporting::IssueSeverity::Medium,
217 category: "Energy".to_string(),
218 description: "Significant energy improvement potential detected".to_string(),
219 location: "Energy analysis".to_string(),
220 suggested_action: "Consider local optimization or different sampling strategy"
221 .to_string(),
222 });
223 }
224 }
225
226 issues
227 }
228
229 fn generate_suggestions(&self, report: &reporting::DebugReport) -> Vec<reporting::Suggestion> {
231 let mut suggestions = Vec::new();
232
233 if let Some(ref constraint_analysis) = report.constraint_analysis {
235 for violation in &constraint_analysis.violations {
236 if !violation.suggested_fixes.is_empty() {
237 suggestions.push(reporting::Suggestion {
238 category: "Constraint Fix".to_string(),
239 description: format!(
240 "Fix violation in constraint: {}",
241 violation
242 .constraint
243 .name
244 .as_ref()
245 .unwrap_or(&"unnamed".to_string())
246 ),
247 impact: violation.violation_amount,
248 feasibility: 0.8,
249 action_steps: violation
250 .suggested_fixes
251 .iter()
252 .map(|fix| fix.description.clone())
253 .collect(),
254 });
255 }
256 }
257 }
258
259 suggestions
260 }
261
262 fn generate_summary(&self, report: &reporting::DebugReport) -> reporting::DebugSummary {
264 let total_issues = report.issues.len();
265 let critical_issues = report
266 .issues
267 .iter()
268 .filter(|i| matches!(i.severity, reporting::IssueSeverity::Critical))
269 .count();
270 let suggestions_count = report.suggestions.len();
271
272 let mut summary = reporting::DebugSummary {
273 total_issues,
274 critical_issues,
275 suggestions_count,
276 ..Default::default()
277 };
278
279 if let Some(ref constraint_analysis) = report.constraint_analysis {
280 summary.constraint_satisfaction_rate = constraint_analysis.satisfaction_rate;
281 }
282
283 if let Some(ref energy_analysis) = report.energy_analysis {
284 summary.total_energy = energy_analysis.total_energy;
285 summary.improvement_potential = energy_analysis.improvement_potential;
286 }
287
288 summary.overall_score = self.calculate_overall_score(&summary);
290
291 summary
292 }
293
294 fn calculate_overall_score(&self, summary: &reporting::DebugSummary) -> f64 {
296 let mut score = 1.0;
297
298 score *= summary.constraint_satisfaction_rate;
300
301 if summary.critical_issues > 0 {
303 score *= 0.5;
304 }
305
306 score *= (1.0 - summary.improvement_potential).max(0.0);
308
309 score.max(0.0).min(1.0)
310 }
311
312 fn estimate_improvement_potential(&self, breakdown: &energy_analyzer::EnergyBreakdown) -> f64 {
314 let current_energy = breakdown.total_energy;
316 let best_nearby = breakdown
317 .energy_landscape
318 .local_minima
319 .iter()
320 .map(|m| m.energy)
321 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
322 .unwrap_or(current_energy);
323
324 if best_nearby < current_energy {
325 ((current_energy - best_nearby) / current_energy.abs()).min(1.0)
326 } else {
327 0.0
328 }
329 }
330}
331
332pub struct InteractiveDebugger {
334 problem_info: types::ProblemInfo,
336 pub current_solution: Option<types::Solution>,
338 debugger: SolutionDebugger,
340 pub watch_variables: Vec<String>,
342}
343
344impl InteractiveDebugger {
345 pub fn new(problem_info: types::ProblemInfo) -> Self {
347 let config = config::DebuggerConfig {
348 detailed_analysis: true,
349 check_constraints: true,
350 analyze_energy: true,
351 compare_solutions: false,
352 generate_visuals: false,
353 output_format: config::DebugOutputFormat::Console,
354 verbosity: config::VerbosityLevel::Normal,
355 };
356
357 Self {
358 debugger: SolutionDebugger::new(problem_info.clone(), config),
359 problem_info,
360 current_solution: None,
361 watch_variables: Vec::new(),
362 }
363 }
364
365 pub fn load_solution(&mut self, solution: types::Solution) {
367 self.current_solution = Some(solution);
368 }
369
370 pub fn add_watch(&mut self, variable: String) {
372 if !self.watch_variables.contains(&variable) {
373 self.watch_variables.push(variable);
374 }
375 }
376
377 pub fn execute_command(&mut self, command: &str) -> String {
379 match command {
380 "help" => "Available commands: help, energy, constraints, watch".to_string(),
381 "energy" => {
382 if let Some(ref solution) = self.current_solution {
383 format!("Solution energy: {}", solution.energy)
384 } else {
385 "No solution loaded".to_string()
386 }
387 }
388 "constraints" => {
389 if let Some(ref solution) = self.current_solution {
390 let report = self.debugger.debug_solution(solution);
391 if let Some(constraint_analysis) = report.constraint_analysis {
392 format!(
393 "Constraint satisfaction rate: {:.2}%",
394 constraint_analysis.satisfaction_rate * 100.0
395 )
396 } else {
397 "No constraint analysis available".to_string()
398 }
399 } else {
400 "No solution loaded".to_string()
401 }
402 }
403 "watch" => {
404 format!("Watched variables: {:?}", self.watch_variables)
405 }
406 _ => format!("Unknown command: {command}"),
407 }
408 }
409}