1use crate::sampler::SampleResult;
7use scirs2_core::ndarray::Array2;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11type JobShopSchedule = Vec<(usize, usize, usize, usize)>;
13
14#[cfg(feature = "scirs")]
15use crate::scirs_stub::{
16 scirs2_graphs::{Graph, GraphLayout},
17 scirs2_plot::{ColorMap, NetworkPlot, Plot2D},
18};
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub enum VisualizationType {
23 TSP {
25 coordinates: Vec<(f64, f64)>,
26 city_names: Option<Vec<String>>,
27 },
28 GraphColoring {
30 adjacency_matrix: Array2<bool>,
31 node_names: Option<Vec<String>>,
32 max_colors: usize,
33 },
34 MaxCut {
36 adjacency_matrix: Array2<f64>,
37 node_names: Option<Vec<String>>,
38 },
39 JobShop {
41 n_jobs: usize,
42 n_machines: usize,
43 time_horizon: usize,
44 },
45 NumberPartition { numbers: Vec<f64> },
47 Knapsack {
49 weights: Vec<f64>,
50 values: Vec<f64>,
51 capacity: f64,
52 },
53 Portfolio {
55 asset_names: Vec<String>,
56 expected_returns: Vec<f64>,
57 risk_matrix: Array2<f64>,
58 },
59 Custom {
61 plot_function: String,
62 metadata: HashMap<String, String>,
63 },
64}
65
66pub struct ProblemVisualizer {
68 problem_type: VisualizationType,
69 samples: Vec<SampleResult>,
70 config: VisualizationConfig,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct VisualizationConfig {
76 pub best_only: bool,
78 pub top_k: usize,
80 pub color_scheme: String,
82 pub node_size: f64,
84 pub edge_width: f64,
86 pub animate: bool,
88 pub animation_speed: f64,
90}
91
92impl Default for VisualizationConfig {
93 fn default() -> Self {
94 Self {
95 best_only: false,
96 top_k: 5,
97 color_scheme: "viridis".to_string(),
98 node_size: 50.0,
99 edge_width: 2.0,
100 animate: false,
101 animation_speed: 2.0,
102 }
103 }
104}
105
106impl ProblemVisualizer {
107 pub const fn new(problem_type: VisualizationType, config: VisualizationConfig) -> Self {
109 Self {
110 problem_type,
111 samples: Vec::new(),
112 config,
113 }
114 }
115
116 pub fn add_samples(&mut self, samples: Vec<SampleResult>) {
118 self.samples.extend(samples);
119 }
120
121 pub fn visualize(&self) -> Result<(), Box<dyn std::error::Error>> {
123 if self.samples.is_empty() {
124 return Err("No samples to visualize".into());
125 }
126
127 match &self.problem_type {
128 VisualizationType::TSP {
129 coordinates,
130 city_names,
131 } => self.visualize_tsp(coordinates, city_names)?,
132 VisualizationType::GraphColoring {
133 adjacency_matrix,
134 node_names,
135 max_colors,
136 } => self.visualize_graph_coloring(adjacency_matrix, node_names, *max_colors)?,
137 VisualizationType::MaxCut {
138 adjacency_matrix,
139 node_names,
140 } => self.visualize_max_cut(adjacency_matrix, node_names)?,
141 VisualizationType::JobShop {
142 n_jobs,
143 n_machines,
144 time_horizon,
145 } => self.visualize_job_shop(*n_jobs, *n_machines, *time_horizon)?,
146 VisualizationType::NumberPartition { numbers } => {
147 self.visualize_number_partition(numbers)?;
148 }
149 VisualizationType::Knapsack {
150 weights,
151 values,
152 capacity,
153 } => self.visualize_knapsack(weights, values, *capacity)?,
154 VisualizationType::Portfolio {
155 asset_names,
156 expected_returns,
157 risk_matrix,
158 } => self.visualize_portfolio(asset_names, expected_returns, risk_matrix)?,
159 VisualizationType::Custom {
160 plot_function,
161 metadata,
162 } => self.visualize_custom(plot_function, metadata)?,
163 }
164
165 Ok(())
166 }
167
168 fn visualize_tsp(
170 &self,
171 coordinates: &[(f64, f64)],
172 city_names: &Option<Vec<String>>,
173 ) -> Result<(), Box<dyn std::error::Error>> {
174 let n_cities = coordinates.len();
175
176 let best_samples = self.get_best_samples();
178
179 #[cfg(feature = "scirs")]
180 {
181 use crate::scirs_stub::scirs2_plot::{Figure, Subplot};
182
183 let mut fig = Figure::new();
184
185 for (idx, sample) in best_samples.iter().enumerate() {
186 if idx >= self.config.top_k {
187 break;
188 }
189
190 let subplot = fig.add_subplot(
191 (self.config.top_k as f64).sqrt().ceil() as usize,
192 (self.config.top_k as f64).sqrt().ceil() as usize,
193 idx + 1,
194 )?;
195
196 let tour = self.extract_tsp_tour(sample, n_cities)?;
198
199 let x: Vec<f64> = coordinates.iter().map(|c| c.0).collect();
201 let y: Vec<f64> = coordinates.iter().map(|c| c.1).collect();
202
203 subplot
204 .scatter(&x, &y)
205 .set_size(self.config.node_size)
206 .set_color("blue");
207
208 for i in 0..tour.len() {
210 let from = tour[i];
211 let to = tour[(i + 1) % tour.len()];
212
213 subplot
214 .plot(
215 &[coordinates[from].0, coordinates[to].0],
216 &[coordinates[from].1, coordinates[to].1],
217 )
218 .set_color("red")
219 .set_linewidth(self.config.edge_width);
220 }
221
222 if let Some(names) = city_names {
224 for (i, name) in names.iter().enumerate() {
225 subplot
226 .text(coordinates[i].0, coordinates[i].1, name)
227 .set_fontsize(8)
228 .set_ha("center");
229 }
230 }
231
232 subplot.set_title(&format!(
233 "Tour {}: Distance = {:.2}",
234 idx + 1,
235 sample.energy
236 ));
237 subplot.set_aspect("equal");
238 }
239
240 fig.suptitle("TSP Solutions");
241 fig.show()?;
242 }
243
244 #[cfg(not(feature = "scirs"))]
245 {
246 let export = TSPExport {
248 coordinates: coordinates.to_vec(),
249 city_names: city_names.clone(),
250 best_tours: best_samples
251 .iter()
252 .take(self.config.top_k)
253 .map(|s| self.extract_tsp_tour(s, n_cities))
254 .collect::<Result<Vec<_>, _>>()?,
255 tour_lengths: best_samples
256 .iter()
257 .take(self.config.top_k)
258 .map(|s| s.energy)
259 .collect(),
260 };
261
262 let json = serde_json::to_string_pretty(&export)?;
263 std::fs::write("tsp_solution.json", json)?;
264 println!("TSP solution exported to tsp_solution.json");
265 }
266
267 Ok(())
268 }
269
270 fn extract_tsp_tour(
272 &self,
273 sample: &SampleResult,
274 n_cities: usize,
275 ) -> Result<Vec<usize>, Box<dyn std::error::Error>> {
276 let mut tour = Vec::new();
277 let mut visited = vec![false; n_cities];
278 let mut current = 0;
279
280 tour.push(current);
281 visited[current] = true;
282
283 for _ in 1..n_cities {
285 let mut next_city = None;
286
287 for (j, &is_visited) in visited.iter().enumerate().take(n_cities) {
288 if !is_visited {
289 let edge_var = format!("x_{current}_{j}");
290 if sample.assignments.get(&edge_var).copied().unwrap_or(false) {
291 next_city = Some(j);
292 break;
293 }
294 }
295 }
296
297 if let Some(next) = next_city {
298 tour.push(next);
299 visited[next] = true;
300 current = next;
301 } else {
302 for (j, is_visited) in visited.iter_mut().enumerate().take(n_cities) {
304 if !*is_visited {
305 tour.push(j);
306 *is_visited = true;
307 current = j;
308 break;
309 }
310 }
311 }
312 }
313
314 Ok(tour)
315 }
316
317 fn visualize_graph_coloring(
319 &self,
320 adjacency: &Array2<bool>,
321 node_names: &Option<Vec<String>>,
322 max_colors: usize,
323 ) -> Result<(), Box<dyn std::error::Error>> {
324 let n_nodes = adjacency.nrows();
325 let best_sample = self.get_best_sample()?;
326
327 let node_colors = self.extract_node_colors(best_sample, n_nodes, max_colors)?;
329
330 #[cfg(feature = "scirs")]
331 {
332 use crate::scirs_stub::scirs2_graphs::spring_layout;
333 use crate::scirs_stub::scirs2_plot::Figure;
334
335 let mut fig = Figure::new();
336 let ax = fig.add_subplot(1, 1, 1)?;
337
338 let mut edges = Vec::new();
340 for i in 0..n_nodes {
341 for j in i + 1..n_nodes {
342 if adjacency[[i, j]] {
343 edges.push((i, j));
344 }
345 }
346 }
347
348 let positions = spring_layout(&edges, n_nodes)?;
350
351 for (i, j) in &edges {
353 ax.plot(
354 &[positions[*i].0, positions[*j].0],
355 &[positions[*i].1, positions[*j].1],
356 )
357 .set_color("gray")
358 .set_alpha(0.5)
359 .set_linewidth(1.0);
360 }
361
362 let color_palette = ["red", "blue", "green", "yellow", "purple", "orange"];
364
365 for i in 0..n_nodes {
366 let color = color_palette[node_colors[i] % color_palette.len()];
367
368 ax.scatter(&[positions[i].0], &[positions[i].1])
369 .set_color(color)
370 .set_size(self.config.node_size)
371 .set_edgecolor("black");
372
373 let label = if let Some(names) = node_names {
375 &names[i]
376 } else {
377 &i.to_string()
378 };
379
380 ax.text(positions[i].0, positions[i].1, label)
381 .set_ha("center")
382 .set_va("center")
383 .set_fontsize(8);
384 }
385
386 ax.set_title(&format!(
387 "Graph Coloring: {} colors used",
388 node_colors.iter().max().unwrap_or(&0) + 1
389 ));
390 ax.set_aspect("equal");
391 ax.axis("off");
392
393 fig.show()?;
394 }
395
396 #[cfg(not(feature = "scirs"))]
397 {
398 let export = GraphColoringExport {
400 n_nodes,
401 edges: self.extract_edges(adjacency),
402 node_colors: node_colors.clone(),
403 node_names: node_names.clone(),
404 n_colors_used: node_colors.iter().max().copied().unwrap_or(0) + 1,
405 };
406
407 let json = serde_json::to_string_pretty(&export)?;
408 std::fs::write("graph_coloring.json", json)?;
409 println!("Graph coloring exported to graph_coloring.json");
410 }
411
412 Ok(())
413 }
414
415 fn extract_node_colors(
417 &self,
418 sample: &SampleResult,
419 n_nodes: usize,
420 max_colors: usize,
421 ) -> Result<Vec<usize>, Box<dyn std::error::Error>> {
422 let mut colors = vec![0; n_nodes];
423
424 for (i, color) in colors.iter_mut().enumerate().take(n_nodes) {
425 for c in 0..max_colors {
426 let var_name = format!("node_{i}_color_{c}");
427 if sample.assignments.get(&var_name).copied().unwrap_or(false) {
428 *color = c;
429 break;
430 }
431 }
432 }
433
434 Ok(colors)
435 }
436
437 fn visualize_max_cut(
439 &self,
440 adjacency: &Array2<f64>,
441 node_names: &Option<Vec<String>>,
442 ) -> Result<(), Box<dyn std::error::Error>> {
443 let n_nodes = adjacency.nrows();
444 let best_sample = self.get_best_sample()?;
445
446 let partition = self.extract_partition(best_sample, n_nodes)?;
448
449 #[cfg(feature = "scirs")]
450 {
451 use crate::scirs_stub::scirs2_plot::Figure;
452
453 let mut fig = Figure::new();
454 let ax = fig.add_subplot(1, 1, 1)?;
455
456 let positions = self.compute_graph_layout(adjacency)?;
458
459 let mut cut_weight = 0.0;
461 for i in 0..n_nodes {
462 for j in i + 1..n_nodes {
463 if adjacency[[i, j]] > 0.0 {
464 let is_cut = partition[i] != partition[j];
465 let color = if is_cut { "red" } else { "gray" };
466 let width = if is_cut { 3.0 } else { 1.0 };
467
468 if is_cut {
469 cut_weight += adjacency[[i, j]];
470 }
471
472 ax.plot(
473 &[positions[i].0, positions[j].0],
474 &[positions[i].1, positions[j].1],
475 )
476 .set_color(color)
477 .set_linewidth(width)
478 .set_alpha(if is_cut { 1.0 } else { 0.3 });
479 }
480 }
481 }
482
483 for i in 0..n_nodes {
485 let color = if partition[i] { "blue" } else { "orange" };
486
487 ax.scatter(&[positions[i].0], &[positions[i].1])
488 .set_color(color)
489 .set_size(self.config.node_size)
490 .set_edgecolor("black");
491
492 let label = if let Some(names) = node_names {
494 &names[i]
495 } else {
496 &i.to_string()
497 };
498
499 ax.text(positions[i].0, positions[i].1, label)
500 .set_ha("center")
501 .set_va("center")
502 .set_fontsize(8);
503 }
504
505 ax.set_title(&format!("Max Cut: Weight = {cut_weight:.2}"));
506 ax.set_aspect("equal");
507 ax.axis("off");
508
509 fig.show()?;
510 }
511
512 Ok(())
513 }
514
515 fn extract_partition(
517 &self,
518 sample: &SampleResult,
519 n_nodes: usize,
520 ) -> Result<Vec<bool>, Box<dyn std::error::Error>> {
521 let mut partition = vec![false; n_nodes];
522
523 for (i, part) in partition.iter_mut().enumerate().take(n_nodes) {
524 let var_name = format!("x_{i}");
525 *part = sample.assignments.get(&var_name).copied().unwrap_or(false);
526 }
527
528 Ok(partition)
529 }
530
531 fn visualize_job_shop(
533 &self,
534 n_jobs: usize,
535 n_machines: usize,
536 time_horizon: usize,
537 ) -> Result<(), Box<dyn std::error::Error>> {
538 let best_sample = self.get_best_sample()?;
539
540 let schedule = self.extract_schedule(best_sample, n_jobs, n_machines, time_horizon)?;
542
543 #[cfg(feature = "scirs")]
544 {
545 use crate::scirs_stub::scirs2_plot::Figure;
546
547 let mut fig = Figure::new();
548 let ax = fig.add_subplot(1, 1, 1)?;
549
550 let colors = ["red", "blue", "green", "yellow", "purple", "orange"];
552
553 for (job, machine, start, duration) in &schedule {
554 let mut y = *machine as f64;
555 let color = colors[*job % colors.len()];
556
557 ax.barh(&[y], &[*duration as f64], &[*start as f64], 0.8)
558 .set_color(color)
559 .set_edgecolor("black")
560 .set_label(&format!("Job {job}"));
561 }
562
563 ax.set_xlabel("Time");
564 ax.set_ylabel("Machine");
565 ax.set_title("Job Shop Schedule");
566 ax.set_ylim(-0.5, n_machines as f64 - 0.5);
567 ax.set_xlim(0.0, time_horizon as f64);
568
569 ax.set_yticks(&(0..n_machines).map(|i| i as f64).collect::<Vec<_>>());
571 ax.set_yticklabels(&(0..n_machines).map(|i| format!("M{i}")).collect::<Vec<_>>());
572
573 ax.legend_unique();
575
576 fig.show()?;
577 }
578
579 Ok(())
580 }
581
582 fn extract_schedule(
584 &self,
585 sample: &SampleResult,
586 n_jobs: usize,
587 n_machines: usize,
588 time_horizon: usize,
589 ) -> Result<JobShopSchedule, Box<dyn std::error::Error>> {
590 let mut schedule = Vec::new();
591
592 for j in 0..n_jobs {
595 for m in 0..n_machines {
596 for t in 0..time_horizon {
597 let var_name = format!("x_{j}_{m}_{t}");
598 if sample.assignments.get(&var_name).copied().unwrap_or(false) {
599 let duration = 5; schedule.push((j, m, t, duration));
602 break;
603 }
604 }
605 }
606 }
607
608 Ok(schedule)
609 }
610
611 fn visualize_number_partition(
613 &self,
614 numbers: &[f64],
615 ) -> Result<(), Box<dyn std::error::Error>> {
616 let best_sample = self.get_best_sample()?;
617 let partition = self.extract_partition(best_sample, numbers.len())?;
618
619 #[cfg(feature = "scirs")]
620 {
621 use crate::scirs_stub::scirs2_plot::Figure;
622
623 let mut fig = Figure::new();
624 let ax = fig.add_subplot(1, 1, 1)?;
625
626 let mut set1 = Vec::new();
628 let mut set2 = Vec::new();
629
630 for (i, &num) in numbers.iter().enumerate() {
631 if partition[i] {
632 set1.push(num);
633 } else {
634 set2.push(num);
635 }
636 }
637
638 let sum1: f64 = set1.iter().sum();
639 let sum2: f64 = set2.iter().sum();
640
641 let mut x_pos = vec![1.0, 2.0];
643 let mut heights = [sum1, sum2];
644 let mut labels = ["Set 1", "Set 2"];
645
646 ax.bar(&[x_pos[0]], &[heights[0]]).set_color("blue");
648 ax.bar(&[x_pos[1]], &[heights[1]]).set_color("orange");
649
650 for (x, h, nums) in &[(1.0, sum1, &set1), (2.0, sum2, &set2)] {
652 ax.text(*x, *h + 0.5, &format!("{h:.2}")).set_ha("center");
653
654 let nums_str = nums
656 .iter()
657 .map(|n| format!("{n:.1}"))
658 .collect::<Vec<_>>()
659 .join(", ");
660 ax.text(*x, -2.0, &nums_str)
661 .set_ha("center")
662 .set_fontsize(8);
663 }
664
665 ax.set_xticks(&x_pos);
666 let string_labels: Vec<String> = labels.iter().map(|s| (*s).to_string()).collect();
667 ax.set_xticklabels(&string_labels);
668 ax.set_ylabel("Sum");
669 ax.set_title(&format!(
670 "Number Partition: |{:.2} - {:.2}| = {:.2}",
671 sum1,
672 sum2,
673 (sum1 - sum2).abs()
674 ));
675
676 fig.show()?;
677 }
678
679 Ok(())
680 }
681
682 fn visualize_knapsack(
684 &self,
685 weights: &[f64],
686 values: &[f64],
687 capacity: f64,
688 ) -> Result<(), Box<dyn std::error::Error>> {
689 let best_sample = self.get_best_sample()?;
690 let n_items = weights.len();
691
692 let mut selected = vec![false; n_items];
694 let mut total_weight = 0.0;
695 let mut total_value = 0.0;
696
697 for i in 0..n_items {
698 let var_name = format!("x_{i}");
699 if best_sample
700 .assignments
701 .get(&var_name)
702 .copied()
703 .unwrap_or(false)
704 {
705 selected[i] = true;
706 total_weight += weights[i];
707 total_value += values[i];
708 }
709 }
710
711 #[cfg(feature = "scirs")]
712 {
713 use crate::scirs_stub::scirs2_plot::Figure;
714
715 let mut fig = Figure::new();
716
717 let ax1 = fig.add_subplot(2, 1, 1)?;
719
720 let x_pos: Vec<f64> = (0..n_items).map(|i| i as f64).collect();
721 for (i, (&value, &is_selected)) in values.iter().zip(selected.iter()).enumerate() {
723 let color = if is_selected { "green" } else { "red" };
724 ax1.bar(&[i as f64], &[value])
725 .set_color(color)
726 .set_alpha(0.7);
727 }
728
729 for (i, (&w, &v)) in weights.iter().zip(values.iter()).enumerate() {
731 ax1.text(i as f64, v + 0.5, &format!("w={w:.1}"))
732 .set_ha("center")
733 .set_fontsize(8);
734 }
735
736 ax1.set_xlabel("Item");
737 ax1.set_ylabel("Value");
738 ax1.set_title(&format!(
739 "Selected Items (Green): Value = {total_value:.2}, Weight = {total_weight:.2}/{capacity:.2}"
740 ));
741
742 let ax2 = fig.add_subplot(2, 1, 2)?;
744
745 ax2.barh(&[1.0], &[total_weight], &[0.0], 0.5)
746 .set_color("blue")
747 .set_label("Used");
748
749 ax2.barh(&[1.0], &[capacity - total_weight], &[total_weight], 0.5)
750 .set_color("lightgray")
751 .set_label("Remaining");
752
753 ax2.axvline(capacity)
754 .set_color("red")
755 .set_linestyle("--")
756 .set_label("Capacity");
757 ax2.set_xlim(0.0, capacity * 1.1);
758 ax2.set_ylim(0.5, 1.5);
759 ax2.set_xlabel("Weight");
760 ax2.set_yticks(&[]);
761 ax2.legend();
762 ax2.set_title("Capacity Utilization");
763
764 fig.show()?;
765 }
766
767 Ok(())
768 }
769
770 fn visualize_portfolio(
772 &self,
773 asset_names: &[String],
774 expected_returns: &[f64],
775 risk_matrix: &Array2<f64>,
776 ) -> Result<(), Box<dyn std::error::Error>> {
777 let best_sample = self.get_best_sample()?;
778 let n_assets = asset_names.len();
779
780 let weights = self.extract_portfolio_weights(best_sample, n_assets)?;
782
783 let portfolio_return: f64 = weights
785 .iter()
786 .zip(expected_returns.iter())
787 .map(|(w, r)| w * r)
788 .sum();
789
790 let portfolio_variance: f64 = weights
791 .iter()
792 .enumerate()
793 .map(|(i, wi)| {
794 weights
795 .iter()
796 .enumerate()
797 .map(|(j, wj)| wi * wj * risk_matrix[[i, j]])
798 .sum::<f64>()
799 })
800 .sum();
801
802 let portfolio_risk = portfolio_variance.sqrt();
803
804 #[cfg(feature = "scirs")]
805 {
806 use crate::scirs_stub::scirs2_plot::Figure;
807
808 let mut fig = Figure::new();
809
810 let ax1 = fig.add_subplot(2, 2, 1)?;
812
813 let nonzero_weights: Vec<(String, f64)> = asset_names
814 .iter()
815 .zip(weights.iter())
816 .filter(|(_, &w)| w > 0.01)
817 .map(|(n, &w)| (n.clone(), w))
818 .collect();
819
820 if !nonzero_weights.is_empty() {
821 let labels: Vec<String> = nonzero_weights.iter().map(|(n, _)| n.clone()).collect();
822 let sizes: Vec<f64> = nonzero_weights.iter().map(|(_, w)| *w).collect();
823
824 ax1.pie(&sizes, &labels).set_autopct("%1.1f%%");
825 ax1.set_title("Portfolio Composition");
826 }
827
828 let ax2 = fig.add_subplot(2, 2, 2)?;
830
831 let risks: Vec<f64> = (0..n_assets).map(|i| risk_matrix[[i, i]].sqrt()).collect();
833
834 ax2.scatter(&risks, expected_returns)
835 .set_color("gray")
836 .set_alpha(0.5)
837 .set_label("Individual Assets");
838
839 ax2.scatter(&[portfolio_risk], &[portfolio_return])
841 .set_color("red")
842 .set_size(100.0)
843 .set_marker("*")
844 .set_label("Portfolio");
845
846 for (i, name) in asset_names.iter().enumerate() {
848 ax2.text(risks[i], expected_returns[i], name)
849 .set_fontsize(8)
850 .set_ha("right");
851 }
852
853 ax2.set_xlabel("Risk (Std Dev)");
854 ax2.set_ylabel("Expected Return");
855 ax2.set_title("Risk-Return Profile");
856 ax2.legend();
857
858 let ax3 = fig.add_subplot(2, 2, 3)?;
860
861 let x_pos: Vec<f64> = (0..n_assets).map(|i| i as f64).collect();
862 ax3.bar(&x_pos, &weights);
863
864 ax3.set_xticks(&x_pos);
865 ax3.set_xticklabels(asset_names);
866 ax3.set_xlabel("Asset");
867 ax3.set_ylabel("Weight");
868 ax3.set_title("Portfolio Weights");
869 ax3.set_ylim(0.0, 1.0);
870
871 for tick in ax3.get_xticklabels() {
872 tick.set_rotation(45);
873 tick.set_ha("right");
874 }
875
876 let ax4 = fig.add_subplot(2, 2, 4)?;
878
879 let summary_text = format!(
880 "Portfolio Statistics\n\n\
881 Expected Return: {:.2}%\n\
882 Risk (Std Dev): {:.2}%\n\
883 Sharpe Ratio: {:.3}\n\
884 Number of Assets: {}",
885 portfolio_return * 100.0,
886 portfolio_risk * 100.0,
887 portfolio_return / portfolio_risk,
888 nonzero_weights.len()
889 );
890
891 let _: () = ax4.trans_axes();
892 ax4.text(0.1, 0.9, &summary_text)
893 .set_fontsize(12)
894 .set_verticalalignment("top")
895 .set_transform(());
896 ax4.axis("off");
897
898 fig.suptitle("Portfolio Optimization Results");
899 fig.tight_layout();
900 fig.show()?;
901 }
902
903 Ok(())
904 }
905
906 fn extract_portfolio_weights(
908 &self,
909 sample: &SampleResult,
910 n_assets: usize,
911 ) -> Result<Vec<f64>, Box<dyn std::error::Error>> {
912 let mut weights = vec![0.0; n_assets];
913
914 let total_selected = (0..n_assets)
920 .filter(|&i| {
921 let var_name = format!("x_{i}");
922 sample.assignments.get(&var_name).copied().unwrap_or(false)
923 })
924 .count();
925
926 if total_selected > 0 {
927 for (i, weight) in weights.iter_mut().enumerate().take(n_assets) {
928 let var_name = format!("x_{i}");
929 if sample.assignments.get(&var_name).copied().unwrap_or(false) {
930 *weight = 1.0 / total_selected as f64;
931 }
932 }
933 }
934
935 Ok(weights)
936 }
937
938 fn visualize_custom(
940 &self,
941 plot_function: &str,
942 metadata: &HashMap<String, String>,
943 ) -> Result<(), Box<dyn std::error::Error>> {
944 println!("Custom visualization: {plot_function} with metadata: {metadata:?}");
946 Ok(())
947 }
948
949 fn get_best_sample(&self) -> Result<&SampleResult, Box<dyn std::error::Error>> {
951 self.samples
952 .iter()
953 .min_by(|a, b| {
954 a.energy
955 .partial_cmp(&b.energy)
956 .unwrap_or(std::cmp::Ordering::Equal)
957 })
958 .ok_or("No samples available".into())
959 }
960
961 fn get_best_samples(&self) -> Vec<&SampleResult> {
963 let mut sorted_samples: Vec<_> = self.samples.iter().collect();
964 sorted_samples.sort_by(|a, b| {
965 a.energy
966 .partial_cmp(&b.energy)
967 .unwrap_or(std::cmp::Ordering::Equal)
968 });
969 sorted_samples
970 }
971
972 fn compute_graph_layout(
974 &self,
975 adjacency: &Array2<f64>,
976 ) -> Result<Vec<(f64, f64)>, Box<dyn std::error::Error>> {
977 let n = adjacency.nrows();
978
979 let mut positions = Vec::new();
981 for i in 0..n {
982 let angle = 2.0 * std::f64::consts::PI * i as f64 / n as f64;
983 positions.push((angle.cos(), angle.sin()));
984 }
985
986 Ok(positions)
987 }
988
989 fn extract_edges(&self, adjacency: &Array2<bool>) -> Vec<(usize, usize)> {
991 let mut edges = Vec::new();
992 let n = adjacency.nrows();
993
994 for i in 0..n {
995 for j in i + 1..n {
996 if adjacency[[i, j]] {
997 edges.push((i, j));
998 }
999 }
1000 }
1001
1002 edges
1003 }
1004}
1005
1006#[derive(Debug, Clone, Serialize, Deserialize)]
1009struct TSPExport {
1010 coordinates: Vec<(f64, f64)>,
1011 city_names: Option<Vec<String>>,
1012 best_tours: Vec<Vec<usize>>,
1013 tour_lengths: Vec<f64>,
1014}
1015
1016#[derive(Debug, Clone, Serialize, Deserialize)]
1017struct GraphColoringExport {
1018 n_nodes: usize,
1019 edges: Vec<(usize, usize)>,
1020 node_colors: Vec<usize>,
1021 node_names: Option<Vec<String>>,
1022 n_colors_used: usize,
1023}