quantrs2_tytan/
visual_problem_builder.rs

1//! Visual problem builder for interactive QUBO construction.
2//!
3//! This module provides a visual interface for building quantum optimization
4//! problems without requiring direct code writing. It includes drag-and-drop
5//! variable creation, constraint specification, and real-time validation.
6
7#![allow(dead_code)]
8
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::fmt;
12
13/// Visual problem builder for interactive QUBO construction
14pub struct VisualProblemBuilder {
15    /// Current problem being built
16    problem: VisualProblem,
17    /// Builder configuration
18    config: BuilderConfig,
19    /// Validation engine
20    validator: ProblemValidator,
21    /// Code generator
22    generator: CodeGenerator,
23    /// Undo/redo stack
24    history: ProblemHistory,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct BuilderConfig {
29    /// Enable real-time validation
30    pub real_time_validation: bool,
31    /// Auto-save interval
32    pub auto_save_interval: Option<std::time::Duration>,
33    /// Maximum problem size
34    pub max_problem_size: usize,
35    /// Default variable type
36    pub default_variable_type: VariableType,
37    /// Export formats supported
38    pub supported_formats: Vec<ExportFormat>,
39    /// Theme settings
40    pub theme: Theme,
41}
42
43impl Default for BuilderConfig {
44    fn default() -> Self {
45        Self {
46            real_time_validation: true,
47            auto_save_interval: Some(std::time::Duration::from_secs(30)),
48            max_problem_size: 10000,
49            default_variable_type: VariableType::Binary,
50            supported_formats: vec![
51                ExportFormat::Python,
52                ExportFormat::Rust,
53                ExportFormat::JSON,
54                ExportFormat::QUBO,
55            ],
56            theme: Theme::default(),
57        }
58    }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Theme {
63    /// Primary color
64    pub primary_color: String,
65    /// Secondary color
66    pub secondary_color: String,
67    /// Background color
68    pub background_color: String,
69    /// Text color
70    pub text_color: String,
71    /// Grid settings
72    pub grid: GridSettings,
73}
74
75impl Default for Theme {
76    fn default() -> Self {
77        Self {
78            primary_color: "#007acc".to_string(),
79            secondary_color: "#ffa500".to_string(),
80            background_color: "#ffffff".to_string(),
81            text_color: "#000000".to_string(),
82            grid: GridSettings::default(),
83        }
84    }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct GridSettings {
89    /// Show grid
90    pub enabled: bool,
91    /// Grid size
92    pub size: usize,
93    /// Grid color
94    pub color: String,
95    /// Snap to grid
96    pub snap: bool,
97}
98
99impl Default for GridSettings {
100    fn default() -> Self {
101        Self {
102            enabled: true,
103            size: 20,
104            color: "#e0e0e0".to_string(),
105            snap: true,
106        }
107    }
108}
109
110/// Visual representation of a problem
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct VisualProblem {
113    /// Problem metadata
114    pub metadata: ProblemMetadata,
115    /// Variables in the problem
116    pub variables: Vec<VisualVariable>,
117    /// Constraints
118    pub constraints: Vec<VisualConstraint>,
119    /// Objective function
120    pub objective: Option<VisualObjective>,
121    /// Visual layout
122    pub layout: ProblemLayout,
123    /// Problem state
124    pub state: ProblemState,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ProblemMetadata {
129    /// Problem name
130    pub name: String,
131    /// Description
132    pub description: String,
133    /// Author
134    pub author: String,
135    /// Creation timestamp
136    pub created: std::time::SystemTime,
137    /// Last modified
138    pub modified: std::time::SystemTime,
139    /// Problem category
140    pub category: ProblemCategory,
141    /// Tags
142    pub tags: Vec<String>,
143    /// Version
144    pub version: String,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub enum ProblemCategory {
149    /// Optimization problem
150    Optimization,
151    /// Decision problem
152    Decision,
153    /// Constraint satisfaction
154    ConstraintSatisfaction,
155    /// Scheduling
156    Scheduling,
157    /// Routing
158    Routing,
159    /// Finance
160    Finance,
161    /// Machine learning
162    MachineLearning,
163    /// Custom category
164    Custom(String),
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct VisualVariable {
169    /// Variable ID
170    pub id: String,
171    /// Variable name
172    pub name: String,
173    /// Variable type
174    pub var_type: VariableType,
175    /// Domain
176    pub domain: VariableDomain,
177    /// Position in visual layout
178    pub position: Position,
179    /// Visual properties
180    pub visual_properties: VariableVisualProperties,
181    /// Description
182    pub description: String,
183    /// Groups/categories
184    pub groups: Vec<String>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub enum VariableType {
189    /// Binary variable (0 or 1)
190    Binary,
191    /// Integer variable
192    Integer { min: i64, max: i64 },
193    /// Real variable
194    Real { min: f64, max: f64 },
195    /// Categorical variable
196    Categorical { options: Vec<String> },
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub enum VariableDomain {
201    /// Binary domain
202    Binary,
203    /// Integer range
204    IntegerRange { min: i64, max: i64 },
205    /// Real range
206    RealRange { min: f64, max: f64 },
207    /// Discrete set
208    Discrete { values: Vec<String> },
209    /// Custom domain
210    Custom { specification: String },
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct Position {
215    /// X coordinate
216    pub x: f64,
217    /// Y coordinate
218    pub y: f64,
219    /// Z coordinate (for 3D layouts)
220    pub z: Option<f64>,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct VariableVisualProperties {
225    /// Color
226    pub color: String,
227    /// Size
228    pub size: f64,
229    /// Shape
230    pub shape: VariableShape,
231    /// Visibility
232    pub visible: bool,
233    /// Label settings
234    pub label: LabelSettings,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub enum VariableShape {
239    Circle,
240    Square,
241    Triangle,
242    Diamond,
243    Hexagon,
244    Custom(String),
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct LabelSettings {
249    /// Show label
250    pub show: bool,
251    /// Label text
252    pub text: Option<String>,
253    /// Font size
254    pub font_size: f64,
255    /// Label position
256    pub position: LabelPosition,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub enum LabelPosition {
261    Top,
262    Bottom,
263    Left,
264    Right,
265    Center,
266    Custom(Position),
267}
268
269/// Visual constraint representation
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct VisualConstraint {
272    /// Constraint ID
273    pub id: String,
274    /// Constraint name
275    pub name: String,
276    /// Constraint type
277    pub constraint_type: ConstraintType,
278    /// Variables involved
279    pub variables: Vec<String>,
280    /// Parameters
281    pub parameters: HashMap<String, ConstraintParameter>,
282    /// Visual representation
283    pub visual_properties: ConstraintVisualProperties,
284    /// Validation status
285    pub validation_status: ValidationStatus,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub enum ConstraintType {
290    /// Linear constraint: sum(a_i * x_i) ≤ b
291    Linear {
292        coefficients: Vec<f64>,
293        operator: ComparisonOperator,
294        rhs: f64,
295    },
296    /// Quadratic constraint
297    Quadratic {
298        matrix: Vec<Vec<f64>>,
299        operator: ComparisonOperator,
300        rhs: f64,
301    },
302    /// Logical constraint
303    Logical { expression: LogicalExpression },
304    /// Cardinality constraint
305    Cardinality {
306        min: Option<usize>,
307        max: Option<usize>,
308    },
309    /// All different constraint
310    AllDifferent,
311    /// Custom constraint
312    Custom { name: String, expression: String },
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub enum ComparisonOperator {
317    LessEqual,
318    GreaterEqual,
319    Equal,
320    LessThan,
321    GreaterThan,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub enum LogicalExpression {
326    And(Vec<String>),
327    Or(Vec<String>),
328    Not(String),
329    Implies(String, String),
330    Equivalent(String, String),
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
334pub enum ConstraintParameter {
335    Integer(i64),
336    Real(f64),
337    String(String),
338    Boolean(bool),
339    Vector(Vec<f64>),
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct ConstraintVisualProperties {
344    /// Color
345    pub color: String,
346    /// Line style
347    pub line_style: LineStyle,
348    /// Thickness
349    pub thickness: f64,
350    /// Connection points
351    pub connections: Vec<Connection>,
352    /// Show equation
353    pub show_equation: bool,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub enum LineStyle {
358    Solid,
359    Dashed,
360    Dotted,
361    DashDot,
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct Connection {
366    /// From variable
367    pub from: String,
368    /// To variable
369    pub to: String,
370    /// Connection type
371    pub connection_type: ConnectionType,
372    /// Path points
373    pub path: Vec<Position>,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub enum ConnectionType {
378    Direct,
379    Curved,
380    Orthogonal,
381    Custom,
382}
383
384/// Visual objective function
385#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct VisualObjective {
387    /// Objective ID
388    pub id: String,
389    /// Objective name
390    pub name: String,
391    /// Objective type
392    pub objective_type: ObjectiveType,
393    /// Expression
394    pub expression: ObjectiveExpression,
395    /// Optimization direction
396    pub direction: OptimizationDirection,
397    /// Visual properties
398    pub visual_properties: ObjectiveVisualProperties,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
402pub enum ObjectiveType {
403    Linear,
404    Quadratic,
405    Polynomial,
406    Custom,
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize)]
410pub enum ObjectiveExpression {
411    Linear {
412        coefficients: HashMap<String, f64>,
413        constant: f64,
414    },
415    Quadratic {
416        linear_terms: HashMap<String, f64>,
417        quadratic_terms: HashMap<(String, String), f64>,
418        constant: f64,
419    },
420    Custom {
421        expression: String,
422    },
423}
424
425#[derive(Debug, Clone, Serialize, Deserialize)]
426pub enum OptimizationDirection {
427    Minimize,
428    Maximize,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize)]
432pub struct ObjectiveVisualProperties {
433    /// Color scheme
434    pub color_scheme: ColorScheme,
435    /// Show as heatmap
436    pub show_heatmap: bool,
437    /// Show contour lines
438    pub show_contours: bool,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize)]
442pub enum ColorScheme {
443    Viridis,
444    Plasma,
445    Inferno,
446    Magma,
447    Blues,
448    Reds,
449    Greens,
450    Custom(Vec<String>),
451}
452
453/// Problem layout information
454#[derive(Debug, Clone, Serialize, Deserialize)]
455pub struct ProblemLayout {
456    /// Layout type
457    pub layout_type: LayoutType,
458    /// Dimensions
459    pub dimensions: Dimensions,
460    /// Auto-layout settings
461    pub auto_layout: AutoLayoutSettings,
462    /// Zoom level
463    pub zoom: f64,
464    /// View center
465    pub center: Position,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize)]
469pub enum LayoutType {
470    /// Free-form layout
471    FreeForm,
472    /// Grid layout
473    Grid { rows: usize, cols: usize },
474    /// Force-directed layout
475    ForceDirected,
476    /// Hierarchical layout
477    Hierarchical,
478    /// Circular layout
479    Circular,
480    /// Custom layout
481    Custom(String),
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct Dimensions {
486    /// Width
487    pub width: f64,
488    /// Height
489    pub height: f64,
490    /// Depth (for 3D)
491    pub depth: Option<f64>,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize)]
495pub struct AutoLayoutSettings {
496    /// Enable auto-layout
497    pub enabled: bool,
498    /// Layout algorithm
499    pub algorithm: LayoutAlgorithm,
500    /// Parameters
501    pub parameters: HashMap<String, f64>,
502}
503
504#[derive(Debug, Clone, Serialize, Deserialize)]
505pub enum LayoutAlgorithm {
506    SpringEmbedder,
507    ForceAtlas2,
508    Fruchterman,
509    Kamada,
510    Custom(String),
511}
512
513/// Problem state tracking
514#[derive(Debug, Clone, Serialize, Deserialize)]
515pub enum ProblemState {
516    /// Problem is being edited
517    Editing,
518    /// Problem is being validated
519    Validating,
520    /// Problem is valid
521    Valid,
522    /// Problem has errors
523    Invalid { errors: Vec<ValidationError> },
524    /// Problem is being solved
525    Solving,
526    /// Problem has solution
527    Solved { solution: ProblemSolution },
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize)]
531pub struct ProblemSolution {
532    /// Variable assignments
533    pub assignments: HashMap<String, f64>,
534    /// Objective value
535    pub objective_value: f64,
536    /// Solution quality
537    pub quality: f64,
538    /// Solving time
539    pub solve_time: std::time::Duration,
540}
541
542/// Validation system
543pub struct ProblemValidator {
544    /// Validation rules
545    rules: Vec<ValidationRule>,
546    /// Error collector
547    errors: Vec<ValidationError>,
548}
549
550#[derive(Debug, Clone, Serialize, Deserialize)]
551pub struct ValidationRule {
552    /// Rule name
553    pub name: String,
554    /// Rule type
555    pub rule_type: ValidationRuleType,
556    /// Severity
557    pub severity: ValidationSeverity,
558    /// Error message template
559    pub message_template: String,
560}
561
562#[derive(Debug, Clone, Serialize, Deserialize)]
563pub enum ValidationRuleType {
564    /// Check variable count
565    VariableCount { min: usize, max: usize },
566    /// Check constraint consistency
567    ConstraintConsistency,
568    /// Check objective function
569    ObjectiveFunction,
570    /// Check variable dependencies
571    VariableDependencies,
572    /// Check domain validity
573    DomainValidity,
574    /// Custom validation
575    Custom(String),
576}
577
578#[derive(Debug, Clone, Serialize, Deserialize)]
579pub enum ValidationSeverity {
580    Info,
581    Warning,
582    Error,
583    Critical,
584}
585
586#[derive(Debug, Clone, Serialize, Deserialize)]
587pub struct ValidationError {
588    /// Error ID
589    pub id: String,
590    /// Error type
591    pub error_type: ValidationRuleType,
592    /// Severity
593    pub severity: ValidationSeverity,
594    /// Message
595    pub message: String,
596    /// Location
597    pub location: Option<ErrorLocation>,
598    /// Suggestions
599    pub suggestions: Vec<String>,
600}
601
602#[derive(Debug, Clone, Serialize, Deserialize)]
603pub enum ErrorLocation {
604    Variable(String),
605    Constraint(String),
606    Objective,
607    Global,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize)]
611pub enum ValidationStatus {
612    Valid,
613    Warning,
614    Error,
615    Unknown,
616}
617
618/// Code generation system
619pub struct CodeGenerator {
620    /// Templates
621    templates: HashMap<ExportFormat, CodeTemplate>,
622    /// Generation settings
623    settings: GenerationSettings,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
627pub enum ExportFormat {
628    /// Python code
629    Python,
630    /// Rust code
631    Rust,
632    /// JSON representation
633    JSON,
634    /// QUBO matrix
635    QUBO,
636    /// MPS format
637    MPS,
638    /// LP format
639    LP,
640    /// Custom format
641    Custom(String),
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize)]
645pub struct CodeTemplate {
646    /// Template content
647    pub template: String,
648    /// Variable placeholders
649    pub placeholders: Vec<String>,
650    /// Template metadata
651    pub metadata: TemplateMetadata,
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize)]
655pub struct TemplateMetadata {
656    /// Template name
657    pub name: String,
658    /// Description
659    pub description: String,
660    /// Version
661    pub version: String,
662    /// Author
663    pub author: String,
664}
665
666#[derive(Debug, Clone, Serialize, Deserialize)]
667pub struct GenerationSettings {
668    /// Include comments
669    pub include_comments: bool,
670    /// Code style
671    pub code_style: CodeStyle,
672    /// Optimization level
673    pub optimization_level: OptimizationLevel,
674}
675
676#[derive(Debug, Clone, Serialize, Deserialize)]
677pub enum CodeStyle {
678    Compact,
679    Readable,
680    Verbose,
681    Custom(HashMap<String, String>),
682}
683
684#[derive(Debug, Clone, Serialize, Deserialize)]
685pub enum OptimizationLevel {
686    None,
687    Basic,
688    Advanced,
689    Aggressive,
690}
691
692/// Undo/redo system
693pub struct ProblemHistory {
694    /// History stack
695    history: Vec<HistoryEntry>,
696    /// Current position
697    current: usize,
698    /// Maximum history size
699    max_size: usize,
700}
701
702#[derive(Debug, Clone)]
703pub struct HistoryEntry {
704    /// Action type
705    pub action_type: ActionType,
706    /// Timestamp
707    pub timestamp: std::time::Instant,
708    /// Problem state before action
709    pub before_state: VisualProblem,
710    /// Problem state after action
711    pub after_state: VisualProblem,
712    /// Action description
713    pub description: String,
714}
715
716#[derive(Debug, Clone)]
717pub enum ActionType {
718    /// Add variable
719    AddVariable(String),
720    /// Remove variable
721    RemoveVariable(String),
722    /// Modify variable
723    ModifyVariable(String),
724    /// Add constraint
725    AddConstraint(String),
726    /// Remove constraint
727    RemoveConstraint(String),
728    /// Modify constraint
729    ModifyConstraint(String),
730    /// Set objective
731    SetObjective,
732    /// Modify objective
733    ModifyObjective,
734    /// Layout change
735    LayoutChange,
736    /// Bulk operation
737    BulkOperation(Vec<Self>),
738}
739
740impl VisualProblemBuilder {
741    /// Create new visual problem builder
742    pub fn new(config: BuilderConfig) -> Self {
743        Self {
744            problem: VisualProblem::new(),
745            config,
746            validator: ProblemValidator::new(),
747            generator: CodeGenerator::new(),
748            history: ProblemHistory::new(100),
749        }
750    }
751
752    /// Create new empty problem
753    pub fn new_problem(&mut self, name: &str) -> Result<(), String> {
754        let problem = VisualProblem {
755            metadata: ProblemMetadata {
756                name: name.to_string(),
757                description: String::new(),
758                author: String::new(),
759                created: std::time::SystemTime::now(),
760                modified: std::time::SystemTime::now(),
761                category: ProblemCategory::Optimization,
762                tags: Vec::new(),
763                version: "1.0.0".to_string(),
764            },
765            variables: Vec::new(),
766            constraints: Vec::new(),
767            objective: None,
768            layout: ProblemLayout {
769                layout_type: LayoutType::FreeForm,
770                dimensions: Dimensions {
771                    width: 1000.0,
772                    height: 800.0,
773                    depth: None,
774                },
775                auto_layout: AutoLayoutSettings {
776                    enabled: false,
777                    algorithm: LayoutAlgorithm::SpringEmbedder,
778                    parameters: HashMap::new(),
779                },
780                zoom: 1.0,
781                center: Position {
782                    x: 500.0,
783                    y: 400.0,
784                    z: None,
785                },
786            },
787            state: ProblemState::Editing,
788        };
789
790        let old_problem = self.problem.clone();
791        self.problem = problem;
792        let current_problem = self.problem.clone();
793        self.record_action(
794            ActionType::BulkOperation(vec![]),
795            &old_problem,
796            &current_problem,
797            "New problem created",
798        );
799
800        Ok(())
801    }
802
803    /// Add variable to problem
804    pub fn add_variable(
805        &mut self,
806        name: &str,
807        var_type: VariableType,
808        position: Position,
809    ) -> Result<String, String> {
810        // Check if variable already exists
811        if self.problem.variables.iter().any(|v| v.name == name) {
812            return Err(format!("Variable '{name}' already exists"));
813        }
814
815        let id = format!("var_{}", self.problem.variables.len());
816
817        let variable = VisualVariable {
818            id: id.clone(),
819            name: name.to_string(),
820            var_type: var_type.clone(),
821            domain: match var_type {
822                VariableType::Binary => VariableDomain::Binary,
823                VariableType::Integer { min, max } => VariableDomain::IntegerRange { min, max },
824                VariableType::Real { min, max } => VariableDomain::RealRange { min, max },
825                VariableType::Categorical { options } => {
826                    VariableDomain::Discrete { values: options }
827                }
828            },
829            position,
830            visual_properties: VariableVisualProperties {
831                color: self.config.theme.primary_color.clone(),
832                size: 20.0,
833                shape: VariableShape::Circle,
834                visible: true,
835                label: LabelSettings {
836                    show: true,
837                    text: Some(name.to_string()),
838                    font_size: 12.0,
839                    position: LabelPosition::Bottom,
840                },
841            },
842            description: String::new(),
843            groups: Vec::new(),
844        };
845
846        let before_state = self.problem.clone();
847        self.problem.variables.push(variable);
848        self.problem.metadata.modified = std::time::SystemTime::now();
849
850        let current_problem = self.problem.clone();
851        self.record_action(
852            ActionType::AddVariable(id.clone()),
853            &before_state,
854            &current_problem,
855            &format!("Added variable '{name}'"),
856        );
857
858        if self.config.real_time_validation {
859            self.validate_problem()?;
860        }
861
862        Ok(id)
863    }
864
865    /// Remove variable
866    pub fn remove_variable(&mut self, variable_id: &str) -> Result<(), String> {
867        let pos = self
868            .problem
869            .variables
870            .iter()
871            .position(|v| v.id == variable_id)
872            .ok_or_else(|| format!("Variable '{variable_id}' not found"))?;
873
874        let before_state = self.problem.clone();
875        let variable = self.problem.variables.remove(pos);
876
877        // Remove variable from constraints
878        for constraint in &mut self.problem.constraints {
879            constraint.variables.retain(|v| v != &variable.id);
880        }
881
882        // Remove empty constraints
883        self.problem.constraints.retain(|c| !c.variables.is_empty());
884
885        self.problem.metadata.modified = std::time::SystemTime::now();
886
887        let current_problem = self.problem.clone();
888        self.record_action(
889            ActionType::RemoveVariable(variable_id.to_string()),
890            &before_state,
891            &current_problem,
892            &format!("Removed variable '{}'", variable.name),
893        );
894
895        if self.config.real_time_validation {
896            self.validate_problem()?;
897        }
898
899        Ok(())
900    }
901
902    /// Add constraint
903    pub fn add_constraint(
904        &mut self,
905        name: &str,
906        constraint_type: ConstraintType,
907        variables: Vec<String>,
908    ) -> Result<String, String> {
909        // Validate that all variables exist
910        for var_id in &variables {
911            if !self.problem.variables.iter().any(|v| &v.id == var_id) {
912                return Err(format!("Variable '{var_id}' not found"));
913            }
914        }
915
916        let id = format!("constraint_{}", self.problem.constraints.len());
917
918        let constraint = VisualConstraint {
919            id: id.clone(),
920            name: name.to_string(),
921            constraint_type,
922            variables,
923            parameters: HashMap::new(),
924            visual_properties: ConstraintVisualProperties {
925                color: self.config.theme.secondary_color.clone(),
926                line_style: LineStyle::Solid,
927                thickness: 2.0,
928                connections: Vec::new(),
929                show_equation: true,
930            },
931            validation_status: ValidationStatus::Unknown,
932        };
933
934        let before_state = self.problem.clone();
935        self.problem.constraints.push(constraint);
936        self.problem.metadata.modified = std::time::SystemTime::now();
937
938        let current_problem = self.problem.clone();
939        self.record_action(
940            ActionType::AddConstraint(id.clone()),
941            &before_state,
942            &current_problem,
943            &format!("Added constraint '{name}'"),
944        );
945
946        if self.config.real_time_validation {
947            self.validate_problem()?;
948        }
949
950        Ok(id)
951    }
952
953    /// Set objective function
954    pub fn set_objective(
955        &mut self,
956        name: &str,
957        expression: ObjectiveExpression,
958        direction: OptimizationDirection,
959    ) -> Result<(), String> {
960        let objective = VisualObjective {
961            id: "objective_0".to_string(),
962            name: name.to_string(),
963            objective_type: match &expression {
964                ObjectiveExpression::Linear { .. } => ObjectiveType::Linear,
965                ObjectiveExpression::Quadratic { .. } => ObjectiveType::Quadratic,
966                ObjectiveExpression::Custom { .. } => ObjectiveType::Custom,
967            },
968            expression,
969            direction,
970            visual_properties: ObjectiveVisualProperties {
971                color_scheme: ColorScheme::Viridis,
972                show_heatmap: false,
973                show_contours: false,
974            },
975        };
976
977        let before_state = self.problem.clone();
978        self.problem.objective = Some(objective);
979        self.problem.metadata.modified = std::time::SystemTime::now();
980
981        let current_problem = self.problem.clone();
982        self.record_action(
983            ActionType::SetObjective,
984            &before_state,
985            &current_problem,
986            &format!("Set objective function '{name}'"),
987        );
988
989        if self.config.real_time_validation {
990            self.validate_problem()?;
991        }
992
993        Ok(())
994    }
995
996    /// Auto-layout variables
997    pub fn auto_layout(&mut self, algorithm: LayoutAlgorithm) -> Result<(), String> {
998        let before_state = self.problem.clone();
999
1000        match algorithm {
1001            LayoutAlgorithm::SpringEmbedder => {
1002                self.apply_spring_layout()?;
1003            }
1004            LayoutAlgorithm::ForceAtlas2 => {
1005                self.apply_force_atlas_layout()?;
1006            }
1007            LayoutAlgorithm::Fruchterman => {
1008                self.apply_fruchterman_layout()?;
1009            }
1010            _ => return Err("Layout algorithm not implemented".to_string()),
1011        }
1012
1013        self.problem.metadata.modified = std::time::SystemTime::now();
1014
1015        let current_problem = self.problem.clone();
1016        self.record_action(
1017            ActionType::LayoutChange,
1018            &before_state,
1019            &current_problem,
1020            &format!("Applied {algorithm:?} layout"),
1021        );
1022
1023        Ok(())
1024    }
1025
1026    /// Validate current problem
1027    pub fn validate_problem(&mut self) -> Result<(), String> {
1028        self.problem.state = ProblemState::Validating;
1029
1030        let errors = self.validator.validate(&self.problem)?;
1031
1032        if errors.is_empty() {
1033            self.problem.state = ProblemState::Valid;
1034        } else {
1035            self.problem.state = ProblemState::Invalid { errors };
1036        }
1037
1038        Ok(())
1039    }
1040
1041    /// Generate code in specified format
1042    pub fn generate_code(&self, format: ExportFormat) -> Result<String, String> {
1043        self.generator.generate(&self.problem, format)
1044    }
1045
1046    /// Export problem as QUBO matrix
1047    // TODO: Fix QUBO export - requires proper access to compiler internals
1048    // pub fn export_qubo(&self) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
1049    //     // Convert visual problem to DSL AST
1050    //     let ast = self.build_ast()?;
1051    //
1052    //     // Use existing compiler to generate QUBO
1053    //     let mut compiler = Compiler::new(ast);
1054    //     compiler.generate_qubo()
1055    // }
1056    /// Undo last action
1057    pub fn undo(&mut self) -> Result<(), String> {
1058        if let Some(entry) = self.history.undo() {
1059            self.problem = entry.before_state.clone();
1060            Ok(())
1061        } else {
1062            Err("Nothing to undo".to_string())
1063        }
1064    }
1065
1066    /// Redo last undone action
1067    pub fn redo(&mut self) -> Result<(), String> {
1068        if let Some(entry) = self.history.redo() {
1069            self.problem = entry.after_state.clone();
1070            Ok(())
1071        } else {
1072            Err("Nothing to redo".to_string())
1073        }
1074    }
1075
1076    /// Get current problem
1077    pub const fn get_problem(&self) -> &VisualProblem {
1078        &self.problem
1079    }
1080
1081    /// Load problem from JSON
1082    pub fn load_problem(&mut self, json: &str) -> Result<(), String> {
1083        let problem: VisualProblem =
1084            serde_json::from_str(json).map_err(|e| format!("Failed to parse JSON: {e}"))?;
1085
1086        let before_state = self.problem.clone();
1087        let _old_problem = self.problem.clone();
1088        self.problem = problem;
1089
1090        let current_problem = self.problem.clone();
1091        self.record_action(
1092            ActionType::BulkOperation(vec![]),
1093            &before_state,
1094            &current_problem,
1095            "Loaded problem from JSON",
1096        );
1097
1098        Ok(())
1099    }
1100
1101    /// Save problem to JSON
1102    pub fn save_problem(&self) -> Result<String, String> {
1103        serde_json::to_string_pretty(&self.problem)
1104            .map_err(|e| format!("Failed to serialize to JSON: {e}"))
1105    }
1106
1107    // TODO: Fix AST building - requires proper access to compiler internals
1108    // /// Convert visual problem to DSL AST
1109    // fn build_ast(&self) -> Result<AST, String> {
1110    //     let mut variables = Vec::new();
1111    //     let mut constraints = Vec::new();
1112    //
1113    //     // Convert variables
1114    //     for var in &self.problem.variables {
1115    //         let domain = match &var.domain {
1116    //             VariableDomain::Binary => crate::problem_dsl::compiler::VariableDomain::Binary,
1117    //             VariableDomain::IntegerRange { min, max } => {
1118    //                 crate::problem_dsl::compiler::VariableDomain::Integer { min: *min, max: *max }
1119    //             }
1120    //             VariableDomain::RealRange { min, max } => {
1121    //                 crate::problem_dsl::compiler::VariableDomain::Real { min: *min, max: *max }
1122    //             }
1123    //             _ => return Err("Unsupported variable domain".to_string()),
1124    //         };
1125    //
1126    //         // TODO: Fix Variable type construction
1127    //         // variables.push(Variable {
1128    //         //     name: var.name.clone(),
1129    //         //     domain,
1130    //         //     indexed: false,
1131    //         //     indices: Vec::new(),
1132    //         // });
1133    //     }
1134    //
1135    //     // Convert constraints
1136    //     for constraint in &self.problem.constraints {
1137    //         // Simplified constraint conversion
1138    //         constraints.push(DslConstraint {
1139    //             name: constraint.name.clone(),
1140    //             expression: Expression::Literal(0.0), // Placeholder
1141    //             penalty: 1.0,
1142    //         });
1143    //     }
1144    //
1145    //     // Build objective
1146    //     let mut objective = if let Some(obj) = &self.problem.objective {
1147    //         match &obj.expression {
1148    //             ObjectiveExpression::Linear { coefficients, constant } => {
1149    //                 // Build linear expression
1150    //                 Expression::Literal(*constant)
1151    //             }
1152    //             _ => Expression::Literal(0.0),
1153    //         }
1154    //     } else {
1155    //         Expression::Literal(0.0)
1156    //     };
1157    //
1158    //     Ok(AST {
1159    //         variables,
1160    //         constraints,
1161    //         objective,
1162    //     })
1163    // }
1164
1165    /// Record action in history
1166    fn record_action(
1167        &mut self,
1168        action_type: ActionType,
1169        before: &VisualProblem,
1170        after: &VisualProblem,
1171        description: &str,
1172    ) {
1173        let entry = HistoryEntry {
1174            action_type,
1175            timestamp: std::time::Instant::now(),
1176            before_state: before.clone(),
1177            after_state: after.clone(),
1178            description: description.to_string(),
1179        };
1180
1181        self.history.push(entry);
1182    }
1183
1184    /// Apply spring-embedder layout
1185    fn apply_spring_layout(&mut self) -> Result<(), String> {
1186        let n = self.problem.variables.len();
1187        if n == 0 {
1188            return Ok(());
1189        }
1190
1191        // Simple spring-embedder algorithm
1192        let mut positions: Vec<(f64, f64)> = self
1193            .problem
1194            .variables
1195            .iter()
1196            .map(|v| (v.position.x, v.position.y))
1197            .collect();
1198
1199        for _ in 0..100 {
1200            let mut forces = vec![(0.0, 0.0); n];
1201
1202            // Repulsive forces
1203            for i in 0..n {
1204                for j in i + 1..n {
1205                    let dx = positions[i].0 - positions[j].0;
1206                    let dy = positions[i].1 - positions[j].1;
1207                    let dist = dx.hypot(dy).max(1.0);
1208
1209                    let force = 1000.0 / (dist * dist);
1210                    let fx = force * dx / dist;
1211                    let fy = force * dy / dist;
1212
1213                    forces[i].0 += fx;
1214                    forces[i].1 += fy;
1215                    forces[j].0 -= fx;
1216                    forces[j].1 -= fy;
1217                }
1218            }
1219
1220            // Attractive forces (simplified)
1221            for constraint in &self.problem.constraints {
1222                for i in 0..constraint.variables.len() {
1223                    for j in i + 1..constraint.variables.len() {
1224                        if let (Some(idx1), Some(idx2)) = (
1225                            self.problem
1226                                .variables
1227                                .iter()
1228                                .position(|v| v.id == constraint.variables[i]),
1229                            self.problem
1230                                .variables
1231                                .iter()
1232                                .position(|v| v.id == constraint.variables[j]),
1233                        ) {
1234                            let dx = positions[idx1].0 - positions[idx2].0;
1235                            let dy = positions[idx1].1 - positions[idx2].1;
1236                            let dist = dx.hypot(dy).max(1.0);
1237
1238                            let force = 0.1 * dist;
1239                            let fx = force * dx / dist;
1240                            let fy = force * dy / dist;
1241
1242                            forces[idx1].0 -= fx;
1243                            forces[idx1].1 -= fy;
1244                            forces[idx2].0 += fx;
1245                            forces[idx2].1 += fy;
1246                        }
1247                    }
1248                }
1249            }
1250
1251            // Update positions
1252            for i in 0..n {
1253                positions[i].0 += forces[i].0 * 0.01;
1254                positions[i].1 += forces[i].1 * 0.01;
1255            }
1256        }
1257
1258        // Update variable positions
1259        for (i, var) in self.problem.variables.iter_mut().enumerate() {
1260            var.position.x = positions[i].0;
1261            var.position.y = positions[i].1;
1262        }
1263
1264        Ok(())
1265    }
1266
1267    /// Apply Force Atlas 2 layout
1268    fn apply_force_atlas_layout(&mut self) -> Result<(), String> {
1269        // Simplified Force Atlas 2
1270        self.apply_spring_layout()
1271    }
1272
1273    /// Apply Fruchterman-Reingold layout
1274    fn apply_fruchterman_layout(&mut self) -> Result<(), String> {
1275        // Simplified Fruchterman-Reingold
1276        self.apply_spring_layout()
1277    }
1278}
1279
1280impl Default for VisualProblem {
1281    fn default() -> Self {
1282        Self::new()
1283    }
1284}
1285
1286impl VisualProblem {
1287    /// Create new empty problem
1288    pub fn new() -> Self {
1289        Self {
1290            metadata: ProblemMetadata {
1291                name: "Untitled".to_string(),
1292                description: String::new(),
1293                author: String::new(),
1294                created: std::time::SystemTime::now(),
1295                modified: std::time::SystemTime::now(),
1296                category: ProblemCategory::Optimization,
1297                tags: Vec::new(),
1298                version: "1.0.0".to_string(),
1299            },
1300            variables: Vec::new(),
1301            constraints: Vec::new(),
1302            objective: None,
1303            layout: ProblemLayout {
1304                layout_type: LayoutType::FreeForm,
1305                dimensions: Dimensions {
1306                    width: 1000.0,
1307                    height: 800.0,
1308                    depth: None,
1309                },
1310                auto_layout: AutoLayoutSettings {
1311                    enabled: false,
1312                    algorithm: LayoutAlgorithm::SpringEmbedder,
1313                    parameters: HashMap::new(),
1314                },
1315                zoom: 1.0,
1316                center: Position {
1317                    x: 500.0,
1318                    y: 400.0,
1319                    z: None,
1320                },
1321            },
1322            state: ProblemState::Editing,
1323        }
1324    }
1325}
1326
1327impl Default for ProblemValidator {
1328    fn default() -> Self {
1329        Self::new()
1330    }
1331}
1332
1333impl ProblemValidator {
1334    /// Create new validator
1335    pub fn new() -> Self {
1336        Self {
1337            rules: Self::default_rules(),
1338            errors: Vec::new(),
1339        }
1340    }
1341
1342    /// Get default validation rules
1343    fn default_rules() -> Vec<ValidationRule> {
1344        vec![
1345            ValidationRule {
1346                name: "Variable count".to_string(),
1347                rule_type: ValidationRuleType::VariableCount { min: 1, max: 10000 },
1348                severity: ValidationSeverity::Error,
1349                message_template: "Problem must have between {min} and {max} variables".to_string(),
1350            },
1351            ValidationRule {
1352                name: "Objective function".to_string(),
1353                rule_type: ValidationRuleType::ObjectiveFunction,
1354                severity: ValidationSeverity::Warning,
1355                message_template: "Problem should have an objective function".to_string(),
1356            },
1357        ]
1358    }
1359
1360    /// Validate problem
1361    pub fn validate(&mut self, problem: &VisualProblem) -> Result<Vec<ValidationError>, String> {
1362        self.errors.clear();
1363
1364        for rule in &self.rules {
1365            match &rule.rule_type {
1366                ValidationRuleType::VariableCount { min, max } => {
1367                    let count = problem.variables.len();
1368                    if count < *min || count > *max {
1369                        self.errors.push(ValidationError {
1370                            id: format!("var_count_{count}"),
1371                            error_type: rule.rule_type.clone(),
1372                            severity: rule.severity.clone(),
1373                            message: rule
1374                                .message_template
1375                                .replace("{min}", &min.to_string())
1376                                .replace("{max}", &max.to_string()),
1377                            location: Some(ErrorLocation::Global),
1378                            suggestions: vec![
1379                                "Add more variables".to_string(),
1380                                "Remove unnecessary variables".to_string(),
1381                            ],
1382                        });
1383                    }
1384                }
1385                ValidationRuleType::ObjectiveFunction => {
1386                    if problem.objective.is_none() {
1387                        self.errors.push(ValidationError {
1388                            id: "missing_objective".to_string(),
1389                            error_type: rule.rule_type.clone(),
1390                            severity: rule.severity.clone(),
1391                            message: rule.message_template.clone(),
1392                            location: Some(ErrorLocation::Objective),
1393                            suggestions: vec!["Add an objective function".to_string()],
1394                        });
1395                    }
1396                }
1397                _ => {}
1398            }
1399        }
1400
1401        Ok(self.errors.clone())
1402    }
1403}
1404
1405impl Default for CodeGenerator {
1406    fn default() -> Self {
1407        Self::new()
1408    }
1409}
1410
1411impl CodeGenerator {
1412    /// Create new code generator
1413    pub fn new() -> Self {
1414        Self {
1415            templates: Self::default_templates(),
1416            settings: GenerationSettings {
1417                include_comments: true,
1418                code_style: CodeStyle::Readable,
1419                optimization_level: OptimizationLevel::Basic,
1420            },
1421        }
1422    }
1423
1424    /// Get default templates
1425    fn default_templates() -> HashMap<ExportFormat, CodeTemplate> {
1426        let mut templates = HashMap::new();
1427
1428        templates.insert(
1429            ExportFormat::Python,
1430            CodeTemplate {
1431                template: r"
1432# Generated quantum optimization problem
1433import numpy as np
1434from quantrs2_tytan import *
1435
1436# Variables
1437{variables}
1438
1439# Objective function
1440{objective}
1441
1442# Constraints
1443{constraints}
1444
1445# Build and solve
1446{solve_code}
1447"
1448                .to_string(),
1449                placeholders: vec![
1450                    "variables".to_string(),
1451                    "objective".to_string(),
1452                    "constraints".to_string(),
1453                    "solve_code".to_string(),
1454                ],
1455                metadata: TemplateMetadata {
1456                    name: "Python Template".to_string(),
1457                    description: "Generate Python code using quantrs2-tytan".to_string(),
1458                    version: "1.0.0".to_string(),
1459                    author: "QuantRS2".to_string(),
1460                },
1461            },
1462        );
1463
1464        templates.insert(
1465            ExportFormat::Rust,
1466            CodeTemplate {
1467                template: r"
1468// Generated quantum optimization problem
1469use quantrs2_tytan::*;
1470
1471fn main() -> Result<(), Box<dyn std::error::Error>> {{
1472    // Variables
1473    {variables}
1474
1475    // Objective function
1476    {objective}
1477
1478    // Constraints
1479    {constraints}
1480
1481    // Build and solve
1482    {solve_code}
1483
1484    Ok(())
1485}}
1486"
1487                .to_string(),
1488                placeholders: vec![
1489                    "variables".to_string(),
1490                    "objective".to_string(),
1491                    "constraints".to_string(),
1492                    "solve_code".to_string(),
1493                ],
1494                metadata: TemplateMetadata {
1495                    name: "Rust Template".to_string(),
1496                    description: "Generate Rust code using quantrs2-tytan".to_string(),
1497                    version: "1.0.0".to_string(),
1498                    author: "QuantRS2".to_string(),
1499                },
1500            },
1501        );
1502
1503        templates
1504    }
1505
1506    /// Generate code
1507    pub fn generate(
1508        &self,
1509        problem: &VisualProblem,
1510        format: ExportFormat,
1511    ) -> Result<String, String> {
1512        match format {
1513            ExportFormat::JSON => serde_json::to_string_pretty(problem)
1514                .map_err(|e| format!("JSON generation error: {e}")),
1515            ExportFormat::Python => self.generate_python_code(problem),
1516            ExportFormat::Rust => self.generate_rust_code(problem),
1517            _ => Err("Format not supported yet".to_string()),
1518        }
1519    }
1520
1521    /// Generate Python code
1522    fn generate_python_code(&self, problem: &VisualProblem) -> Result<String, String> {
1523        let template = self
1524            .templates
1525            .get(&ExportFormat::Python)
1526            .ok_or("Python template not found")?;
1527
1528        let mut code = template.template.clone();
1529
1530        // Generate variables
1531        let variables_code = problem
1532            .variables
1533            .iter()
1534            .map(|v| format!("{} = symbols(\"{}\")", v.name, v.name))
1535            .collect::<Vec<_>>()
1536            .join("\n");
1537
1538        // Generate objective
1539        let objective_code = if let Some(obj) = &problem.objective {
1540            match &obj.expression {
1541                ObjectiveExpression::Linear {
1542                    coefficients,
1543                    constant,
1544                } => {
1545                    let terms: Vec<String> = coefficients
1546                        .iter()
1547                        .map(|(var, coef)| {
1548                            if *coef == 1.0 {
1549                                var.clone()
1550                            } else {
1551                                format!("{coef} * {var}")
1552                            }
1553                        })
1554                        .collect();
1555
1556                    if *constant == 0.0 {
1557                        format!("h = {}", terms.join(" + "))
1558                    } else {
1559                        format!("h = {} + {}", terms.join(" + "), constant)
1560                    }
1561                }
1562                _ => "h = 0  # Complex objective not implemented".to_string(),
1563            }
1564        } else {
1565            "h = 0  # No objective function".to_string()
1566        };
1567
1568        // Generate constraints
1569        let constraints_code = problem
1570            .constraints
1571            .iter()
1572            .map(|c| format!("# Constraint: {}", c.name))
1573            .collect::<Vec<_>>()
1574            .join("\n");
1575
1576        // Generate solve code
1577        let solve_code = r"
1578# Compile to QUBO
1579qubo, offset = Compile(h).get_qubo()
1580
1581# Choose solver
1582solver = SASampler()
1583
1584# Solve
1585result = solver.run_qubo(qubo, 100)
1586
1587# Display results
1588for r in result:
1589    print(r)
1590"
1591        .to_string();
1592
1593        code = code.replace("{variables}", &variables_code);
1594        code = code.replace("{objective}", &objective_code);
1595        code = code.replace("{constraints}", &constraints_code);
1596        code = code.replace("{solve_code}", &solve_code);
1597
1598        Ok(code)
1599    }
1600
1601    /// Generate Rust code
1602    fn generate_rust_code(&self, problem: &VisualProblem) -> Result<String, String> {
1603        let template = self
1604            .templates
1605            .get(&ExportFormat::Rust)
1606            .ok_or("Rust template not found")?;
1607
1608        let mut code = template.template.clone();
1609
1610        // Generate variables
1611        let variables_code = problem
1612            .variables
1613            .iter()
1614            .map(|v| format!("    let {} = symbols(\"{}\");", v.name, v.name))
1615            .collect::<Vec<_>>()
1616            .join("\n");
1617
1618        // Generate objective (simplified)
1619        let objective_code = "    let h = x; // Simplified objective".to_string();
1620
1621        // Generate constraints
1622        let constraints_code = "    // Constraints not implemented in template".to_string();
1623
1624        // Generate solve code
1625        let solve_code = r#"
1626    // Compile to QUBO
1627    let (qubo, offset) = Compile::new(&h).get_qubo()?;
1628
1629    // Choose solver
1630    let solver = SASampler::new(None);
1631
1632    // Solve
1633    let mut result = solver.run_qubo(&qubo, 100)?;
1634
1635    // Display results
1636    for r in &result {
1637        println!("{:?}", r);
1638    }"#
1639        .to_string();
1640
1641        code = code.replace("{variables}", &variables_code);
1642        code = code.replace("{objective}", &objective_code);
1643        code = code.replace("{constraints}", &constraints_code);
1644        code = code.replace("{solve_code}", &solve_code);
1645
1646        Ok(code)
1647    }
1648}
1649
1650impl ProblemHistory {
1651    /// Create new history
1652    pub const fn new(max_size: usize) -> Self {
1653        Self {
1654            history: Vec::new(),
1655            current: 0,
1656            max_size,
1657        }
1658    }
1659
1660    /// Push new entry
1661    pub fn push(&mut self, entry: HistoryEntry) {
1662        // Remove entries after current position
1663        self.history.truncate(self.current);
1664
1665        // Add new entry
1666        self.history.push(entry);
1667        self.current = self.history.len();
1668
1669        // Trim if too large
1670        if self.history.len() > self.max_size {
1671            self.history.remove(0);
1672            self.current -= 1;
1673        }
1674    }
1675
1676    /// Undo operation
1677    pub fn undo(&mut self) -> Option<&HistoryEntry> {
1678        if self.current > 0 {
1679            self.current -= 1;
1680            self.history.get(self.current)
1681        } else {
1682            None
1683        }
1684    }
1685
1686    /// Redo operation
1687    pub fn redo(&mut self) -> Option<&HistoryEntry> {
1688        if self.current < self.history.len() {
1689            let entry = self.history.get(self.current);
1690            self.current += 1;
1691            entry
1692        } else {
1693            None
1694        }
1695    }
1696}
1697
1698impl fmt::Display for VisualProblem {
1699    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1700        write!(
1701            f,
1702            "Problem '{}': {} variables, {} constraints",
1703            self.metadata.name,
1704            self.variables.len(),
1705            self.constraints.len()
1706        )
1707    }
1708}
1709
1710#[cfg(test)]
1711mod tests {
1712    use super::*;
1713
1714    #[test]
1715    fn test_visual_problem_builder() -> Result<(), String> {
1716        let config = BuilderConfig::default();
1717        let mut builder = VisualProblemBuilder::new(config);
1718
1719        // Create new problem
1720        builder.new_problem("Test Problem")?;
1721
1722        // Add variables
1723        let var1_id = builder.add_variable(
1724            "x1",
1725            VariableType::Binary,
1726            Position {
1727                x: 100.0,
1728                y: 100.0,
1729                z: None,
1730            },
1731        )?;
1732
1733        let var2_id = builder.add_variable(
1734            "x2",
1735            VariableType::Binary,
1736            Position {
1737                x: 200.0,
1738                y: 100.0,
1739                z: None,
1740            },
1741        )?;
1742
1743        assert_eq!(builder.problem.variables.len(), 2);
1744
1745        // Add constraint
1746        let _constraint_id = builder.add_constraint(
1747            "Sum constraint",
1748            ConstraintType::Linear {
1749                coefficients: vec![1.0, 1.0],
1750                operator: ComparisonOperator::LessEqual,
1751                rhs: 1.0,
1752            },
1753            vec![var1_id.clone(), var2_id.clone()],
1754        )?;
1755
1756        assert_eq!(builder.problem.constraints.len(), 1);
1757
1758        // Set objective
1759        let mut coefficients = HashMap::new();
1760        coefficients.insert(var1_id, 1.0);
1761        coefficients.insert(var2_id, 2.0);
1762
1763        builder.set_objective(
1764            "Linear objective",
1765            ObjectiveExpression::Linear {
1766                coefficients,
1767                constant: 0.0,
1768            },
1769            OptimizationDirection::Maximize,
1770        )?;
1771
1772        assert!(builder.problem.objective.is_some());
1773
1774        // Test undo/redo
1775        builder.undo()?;
1776        assert!(builder.problem.objective.is_none());
1777
1778        builder.redo()?;
1779        assert!(builder.problem.objective.is_some());
1780
1781        // Test code generation
1782        let python_code = builder.generate_code(ExportFormat::Python)?;
1783        assert!(python_code.contains("symbols"));
1784        assert!(python_code.contains("SASampler"));
1785
1786        // Test JSON export
1787        let json = builder.save_problem()?;
1788        assert!(json.contains("Test Problem"));
1789
1790        Ok(())
1791    }
1792
1793    #[test]
1794    fn test_validation() -> Result<(), String> {
1795        let mut validator = ProblemValidator::new();
1796        let mut problem = VisualProblem::new();
1797
1798        // Empty problem should have errors
1799        let errors = validator.validate(&problem)?;
1800        assert!(!errors.is_empty());
1801
1802        // Add variables
1803        problem.variables.push(VisualVariable {
1804            id: "var1".to_string(),
1805            name: "x1".to_string(),
1806            var_type: VariableType::Binary,
1807            domain: VariableDomain::Binary,
1808            position: Position {
1809                x: 0.0,
1810                y: 0.0,
1811                z: None,
1812            },
1813            visual_properties: VariableVisualProperties {
1814                color: "#000000".to_string(),
1815                size: 10.0,
1816                shape: VariableShape::Circle,
1817                visible: true,
1818                label: LabelSettings {
1819                    show: true,
1820                    text: None,
1821                    font_size: 12.0,
1822                    position: LabelPosition::Bottom,
1823                },
1824            },
1825            description: String::new(),
1826            groups: Vec::new(),
1827        });
1828
1829        // Problem with variables but no objective should have warnings
1830        let errors = validator.validate(&problem)?;
1831        assert!(errors
1832            .iter()
1833            .any(|e| matches!(e.severity, ValidationSeverity::Warning)));
1834
1835        Ok(())
1836    }
1837}