Skip to main content

quantrs2_anneal/rl_embedding_optimizer/
embedding.rs

1//! Core embedding optimization logic for RL embedding optimizer
2
3use std::collections::HashMap;
4use std::time::Instant;
5
6use super::error::{RLEmbeddingError, RLEmbeddingResult};
7use super::state_action::StateActionProcessor;
8use super::types::{EmbeddingAction, EmbeddingState, ObjectiveWeights};
9use crate::embedding::{Embedding, HardwareTopology};
10use crate::ising::IsingModel;
11
12/// Embedding evaluation and optimization utilities
13pub struct EmbeddingOptimizer;
14
15impl EmbeddingOptimizer {
16    /// Generate initial embedding using baseline method
17    pub fn generate_initial_embedding(
18        problem: &IsingModel,
19        hardware: &HardwareTopology,
20    ) -> RLEmbeddingResult<Embedding> {
21        // Use a simple greedy embedding as baseline
22        let mut embedding = Embedding {
23            chains: HashMap::new(),
24            qubit_to_variable: HashMap::new(),
25        };
26
27        // Simple mapping: logical qubit i -> physical qubit i % num_physical
28        for logical in 0..problem.num_qubits {
29            let physical = logical % StateActionProcessor::get_num_qubits(hardware);
30            embedding.chains.insert(logical, vec![physical]);
31            // Chain strength would be stored elsewhere if needed
32        }
33
34        Ok(embedding)
35    }
36
37    /// Calculate reward for action
38    pub fn calculate_reward(
39        old_embedding: &Embedding,
40        new_embedding: &Embedding,
41        hardware: &HardwareTopology,
42        objective_weights: &ObjectiveWeights,
43    ) -> RLEmbeddingResult<f64> {
44        let old_quality = Self::evaluate_embedding_quality(old_embedding, hardware)?;
45        let new_quality = Self::evaluate_embedding_quality(new_embedding, hardware)?;
46
47        let improvement = new_quality - old_quality;
48
49        // Multi-objective reward
50        let mut reward = 0.0;
51
52        // Reward for quality improvement
53        reward += improvement * objective_weights.efficiency_weight;
54
55        // Penalty for increased chain lengths
56        let old_avg_chain_length = Self::calculate_average_chain_length(old_embedding);
57        let new_avg_chain_length = Self::calculate_average_chain_length(new_embedding);
58        let chain_penalty =
59            (new_avg_chain_length - old_avg_chain_length) * objective_weights.chain_length_weight;
60        reward -= chain_penalty;
61
62        // Reward for better hardware utilization
63        let old_utilization = Self::calculate_hardware_utilization(old_embedding, hardware);
64        let new_utilization = Self::calculate_hardware_utilization(new_embedding, hardware);
65        let utilization_reward =
66            (new_utilization - old_utilization) * objective_weights.utilization_weight;
67        reward += utilization_reward;
68
69        Ok(reward)
70    }
71
72    /// Evaluate embedding quality
73    pub fn evaluate_embedding_quality(
74        embedding: &Embedding,
75        hardware: &HardwareTopology,
76    ) -> RLEmbeddingResult<f64> {
77        let mut quality = 0.0;
78
79        // Factor 1: Chain length penalty
80        let avg_chain_length = Self::calculate_average_chain_length(embedding);
81        quality -= avg_chain_length * 0.1;
82
83        // Factor 2: Hardware utilization
84        let utilization = Self::calculate_hardware_utilization(embedding, hardware);
85        quality += utilization * 0.5;
86
87        // Factor 3: Connectivity preservation
88        let connectivity = Self::calculate_connectivity_preservation(embedding, hardware);
89        quality += connectivity * 0.3;
90
91        // Factor 4: Compactness
92        let compactness = Self::calculate_embedding_compactness(embedding, hardware);
93        quality += compactness * 0.1;
94
95        Ok(quality)
96    }
97
98    /// Calculate average chain length
99    pub fn calculate_average_chain_length(embedding: &Embedding) -> f64 {
100        if embedding.chains.is_empty() {
101            return 0.0;
102        }
103
104        let total_length: usize = embedding.chains.values().map(std::vec::Vec::len).sum();
105        total_length as f64 / embedding.chains.len() as f64
106    }
107
108    /// Calculate hardware utilization
109    #[must_use]
110    pub fn calculate_hardware_utilization(
111        embedding: &Embedding,
112        hardware: &HardwareTopology,
113    ) -> f64 {
114        let used_qubits: std::collections::HashSet<usize> =
115            embedding.chains.values().flatten().copied().collect();
116
117        used_qubits.len() as f64 / StateActionProcessor::get_num_qubits(hardware) as f64
118    }
119
120    /// Calculate connectivity preservation
121    fn calculate_connectivity_preservation(
122        embedding: &Embedding,
123        hardware: &HardwareTopology,
124    ) -> f64 {
125        // Simplified: assume good connectivity preservation for valid embeddings
126        if embedding.chains.is_empty() {
127            0.0
128        } else {
129            0.8 // Placeholder
130        }
131    }
132
133    /// Calculate embedding compactness
134    fn calculate_embedding_compactness(embedding: &Embedding, hardware: &HardwareTopology) -> f64 {
135        // Simplified compactness measure
136        let avg_chain_length = Self::calculate_average_chain_length(embedding);
137        1.0 / (1.0 + avg_chain_length)
138    }
139
140    /// Update state after action
141    pub fn update_state(
142        old_state: &EmbeddingState,
143        action: &EmbeddingAction,
144        new_embedding: &Embedding,
145        hardware: &HardwareTopology,
146    ) -> RLEmbeddingResult<EmbeddingState> {
147        let mut new_state = old_state.clone();
148
149        // Update embedding state
150        new_state.embedding_state.logical_to_physical = new_embedding.chains.clone();
151        new_state.embedding_state.chain_lengths = new_embedding
152            .chains
153            .values()
154            .map(std::vec::Vec::len)
155            .collect();
156
157        // Update efficiency metrics
158        new_state
159            .embedding_state
160            .efficiency_metrics
161            .avg_chain_length = Self::calculate_average_chain_length(new_embedding);
162        new_state
163            .embedding_state
164            .efficiency_metrics
165            .max_chain_length = new_embedding
166            .chains
167            .values()
168            .map(std::vec::Vec::len)
169            .max()
170            .unwrap_or(0);
171        new_state
172            .embedding_state
173            .efficiency_metrics
174            .utilization_ratio = Self::calculate_hardware_utilization(new_embedding, hardware);
175
176        // Update quality score
177        new_state.embedding_state.quality_score =
178            Self::evaluate_embedding_quality(new_embedding, hardware)?;
179
180        // Update performance history
181        new_state
182            .performance_history
183            .push(new_state.embedding_state.quality_score);
184        if new_state.performance_history.len() > 10 {
185            new_state.performance_history.remove(0);
186        }
187
188        Ok(new_state)
189    }
190
191    /// Check if state is terminal
192    #[must_use]
193    pub fn is_terminal_state(state: &EmbeddingState) -> bool {
194        // Terminal if quality score is very high or improvement has plateaued
195        state.embedding_state.quality_score > 0.95
196            || (state.performance_history.len() >= 5
197                && state
198                    .performance_history
199                    .windows(2)
200                    .all(|w| (w[1] - w[0]).abs() < 0.001))
201    }
202
203    /// Calculate problem density
204    #[must_use]
205    pub fn calculate_problem_density(problem: &IsingModel) -> f64 {
206        let mut num_edges = 0;
207        for i in 0..problem.num_qubits {
208            for j in (i + 1)..problem.num_qubits {
209                if let Ok(coupling) = problem.get_coupling(i, j) {
210                    if coupling.abs() > 1e-10 {
211                        num_edges += 1;
212                    }
213                }
214            }
215        }
216
217        2.0 * f64::from(num_edges) / (problem.num_qubits * (problem.num_qubits - 1)) as f64
218    }
219
220    /// Classify problem type for transfer learning
221    #[must_use]
222    pub fn classify_problem_type(problem: &IsingModel) -> String {
223        let density = Self::calculate_problem_density(problem);
224
225        if density > 0.8 {
226            "dense_random".to_string()
227        } else if density < 0.1 {
228            "sparse_structured".to_string()
229        } else if problem.num_qubits < 50 {
230            "small_optimization".to_string()
231        } else {
232            "large_optimization".to_string()
233        }
234    }
235}