quantrs2_core/qml/
nlp.rs

1//! Quantum Machine Learning for Natural Language Processing
2//!
3//! This module provides specialized quantum machine learning layers and algorithms
4//! optimized for natural language processing tasks such as text classification,
5//! sentiment analysis, and language modeling.
6
7use super::{Parameter, QMLLayer};
8use crate::{
9    error::{QuantRS2Error, QuantRS2Result},
10    gate::{multi::*, single::*, GateOp},
11    parametric::{ParametricRotationX, ParametricRotationY, ParametricRotationZ},
12    qubit::QubitId,
13};
14use ndarray::Array1;
15use num_complex::Complex64;
16use std::collections::HashMap;
17use std::f64::consts::PI;
18
19/// Text embedding strategies for quantum NLP
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum TextEmbeddingStrategy {
22    /// Word-level embeddings: each word is encoded separately
23    WordLevel,
24    /// Character-level embeddings: each character is encoded
25    CharLevel,
26    /// N-gram embeddings: overlapping n-grams are encoded
27    NGram(usize),
28    /// Token embeddings with positional encoding
29    TokenPositional,
30    /// Hierarchical embeddings: words -> sentences -> documents
31    Hierarchical,
32}
33
34/// Configuration for quantum NLP models
35#[derive(Debug, Clone)]
36pub struct QNLPConfig {
37    /// Number of qubits for text representation
38    pub text_qubits: usize,
39    /// Number of qubits for feature extraction
40    pub feature_qubits: usize,
41    /// Maximum sequence length
42    pub max_sequence_length: usize,
43    /// Vocabulary size
44    pub vocab_size: usize,
45    /// Embedding dimension
46    pub embedding_dim: usize,
47    /// Text embedding strategy
48    pub embedding_strategy: TextEmbeddingStrategy,
49    /// Number of attention heads (for quantum attention)
50    pub num_attention_heads: usize,
51    /// Hidden dimension for feedforward layers
52    pub hidden_dim: usize,
53}
54
55impl Default for QNLPConfig {
56    fn default() -> Self {
57        Self {
58            text_qubits: 8,
59            feature_qubits: 4,
60            max_sequence_length: 32,
61            vocab_size: 1000,
62            embedding_dim: 64,
63            embedding_strategy: TextEmbeddingStrategy::WordLevel,
64            num_attention_heads: 4,
65            hidden_dim: 128,
66        }
67    }
68}
69
70/// Quantum word embedding layer
71pub struct QuantumWordEmbedding {
72    /// Configuration
73    config: QNLPConfig,
74    /// Embedding parameters for each word in vocabulary
75    embeddings: Vec<Vec<Parameter>>,
76    /// Number of qubits
77    num_qubits: usize,
78}
79
80impl QuantumWordEmbedding {
81    /// Create a new quantum word embedding layer
82    pub fn new(config: QNLPConfig) -> Self {
83        let num_qubits = config.text_qubits;
84        let mut embeddings = Vec::new();
85
86        // Initialize embeddings for each word in vocabulary
87        for word_id in 0..config.vocab_size {
88            let mut word_embedding = Vec::new();
89            for qubit in 0..num_qubits {
90                // Initialize with random values
91                let value = ((word_id * qubit) as f64 * 0.1).sin() * 0.5;
92                word_embedding.push(Parameter {
93                    name: format!("embed_{}_{}", word_id, qubit),
94                    value,
95                    bounds: None,
96                });
97            }
98            embeddings.push(word_embedding);
99        }
100
101        Self {
102            config,
103            embeddings,
104            num_qubits,
105        }
106    }
107
108    /// Encode a sequence of word IDs into quantum gates
109    pub fn encode_sequence(&self, word_ids: &[usize]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
110        let mut gates: Vec<Box<dyn GateOp>> = Vec::new();
111
112        for (position, &word_id) in word_ids.iter().enumerate() {
113            if word_id >= self.config.vocab_size {
114                return Err(QuantRS2Error::InvalidInput(format!(
115                    "Word ID {} exceeds vocabulary size {}",
116                    word_id, self.config.vocab_size
117                )));
118            }
119
120            if position >= self.config.max_sequence_length {
121                break; // Truncate sequence if too long
122            }
123
124            // Encode word at this position
125            let word_embedding = &self.embeddings[word_id];
126            for (qubit_idx, param) in word_embedding.iter().enumerate() {
127                let qubit = QubitId(qubit_idx as u32);
128
129                // Use rotation gates to encode the embedding values
130                gates.push(Box::new(ParametricRotationY {
131                    target: qubit,
132                    theta: crate::parametric::Parameter::Constant(param.value * PI), // Scale to appropriate range
133                }));
134
135                // Add positional encoding
136                let positional_angle =
137                    (position as f64) / (self.config.max_sequence_length as f64) * PI;
138                gates.push(Box::new(ParametricRotationZ {
139                    target: qubit,
140                    theta: crate::parametric::Parameter::Constant(positional_angle * 0.1), // Smaller contribution
141                }));
142            }
143        }
144
145        Ok(gates)
146    }
147}
148
149impl QMLLayer for QuantumWordEmbedding {
150    fn num_qubits(&self) -> usize {
151        self.num_qubits
152    }
153
154    fn parameters(&self) -> &[Parameter] {
155        // Flatten all embeddings into a single parameter vector
156        // This is a simplified approach - in practice might want more efficient storage
157        unimplemented!("Use flatten_parameters() method instead")
158    }
159
160    fn parameters_mut(&mut self) -> &mut [Parameter] {
161        unimplemented!("Use flatten_parameters_mut() method instead")
162    }
163
164    fn gates(&self) -> Vec<Box<dyn GateOp>> {
165        // Return empty - this layer provides encoding method
166        Vec::new()
167    }
168
169    fn compute_gradients(
170        &self,
171        _state: &Array1<Complex64>,
172        _loss_gradient: &Array1<Complex64>,
173    ) -> QuantRS2Result<Vec<f64>> {
174        // Placeholder for gradient computation
175        let total_params = self.config.vocab_size * self.num_qubits;
176        Ok(vec![0.0; total_params])
177    }
178
179    fn name(&self) -> &str {
180        "QuantumWordEmbedding"
181    }
182}
183
184/// Quantum attention mechanism for NLP
185pub struct QuantumAttention {
186    /// Number of qubits
187    num_qubits: usize,
188    /// Number of attention heads
189    num_heads: usize,
190    /// Query parameters
191    query_params: Vec<Parameter>,
192    /// Key parameters  
193    key_params: Vec<Parameter>,
194    /// Value parameters
195    value_params: Vec<Parameter>,
196    /// Output projection parameters
197    output_params: Vec<Parameter>,
198}
199
200impl QuantumAttention {
201    /// Create a new quantum attention layer
202    pub fn new(num_qubits: usize, num_heads: usize) -> Self {
203        let params_per_head = num_qubits / num_heads;
204
205        let mut query_params = Vec::new();
206        let mut key_params = Vec::new();
207        let mut value_params = Vec::new();
208        let mut output_params = Vec::new();
209
210        // Initialize parameters for each head
211        for head in 0..num_heads {
212            for i in 0..params_per_head {
213                // Query parameters
214                query_params.push(Parameter {
215                    name: format!("query_{}_{}", head, i),
216                    value: ((head + i) as f64 * 0.1).sin() * 0.5,
217                    bounds: None,
218                });
219
220                // Key parameters
221                key_params.push(Parameter {
222                    name: format!("key_{}_{}", head, i),
223                    value: ((head + i + 1) as f64 * 0.1).cos() * 0.5,
224                    bounds: None,
225                });
226
227                // Value parameters
228                value_params.push(Parameter {
229                    name: format!("value_{}_{}", head, i),
230                    value: ((head + i + 2) as f64 * 0.1).sin() * 0.5,
231                    bounds: None,
232                });
233
234                // Output parameters
235                output_params.push(Parameter {
236                    name: format!("output_{}_{}", head, i),
237                    value: ((head + i + 3) as f64 * 0.1).cos() * 0.5,
238                    bounds: None,
239                });
240            }
241        }
242
243        Self {
244            num_qubits,
245            num_heads,
246            query_params,
247            key_params,
248            value_params,
249            output_params,
250        }
251    }
252
253    /// Generate attention gates for a sequence
254    pub fn attention_gates(&self) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
255        let mut gates: Vec<Box<dyn GateOp>> = Vec::new();
256        let params_per_head = self.num_qubits / self.num_heads;
257
258        // For each attention head
259        for head in 0..self.num_heads {
260            let head_offset = head * params_per_head;
261
262            // Apply query transformations
263            for i in 0..params_per_head {
264                let qubit = QubitId((head_offset + i) as u32);
265                let param_idx = head * params_per_head + i;
266
267                gates.push(Box::new(ParametricRotationY {
268                    target: qubit,
269                    theta: crate::parametric::Parameter::Constant(
270                        self.query_params[param_idx].value,
271                    ),
272                }));
273            }
274
275            // Apply key transformations
276            for i in 0..params_per_head {
277                let qubit = QubitId((head_offset + i) as u32);
278                let param_idx = head * params_per_head + i;
279
280                gates.push(Box::new(ParametricRotationZ {
281                    target: qubit,
282                    theta: crate::parametric::Parameter::Constant(self.key_params[param_idx].value),
283                }));
284            }
285
286            // Add entanglement within head (for attention computation)
287            for i in 0..params_per_head - 1 {
288                let control = QubitId((head_offset + i) as u32);
289                let target = QubitId((head_offset + i + 1) as u32);
290                gates.push(Box::new(CNOT { control, target }));
291            }
292
293            // Apply value transformations
294            for i in 0..params_per_head {
295                let qubit = QubitId((head_offset + i) as u32);
296                let param_idx = head * params_per_head + i;
297
298                gates.push(Box::new(ParametricRotationX {
299                    target: qubit,
300                    theta: crate::parametric::Parameter::Constant(
301                        self.value_params[param_idx].value,
302                    ),
303                }));
304            }
305        }
306
307        // Add inter-head entanglement (for multi-head attention)
308        for head in 0..self.num_heads - 1 {
309            let control = QubitId((head * params_per_head) as u32);
310            let target = QubitId(((head + 1) * params_per_head) as u32);
311            gates.push(Box::new(CNOT { control, target }));
312        }
313
314        // Apply output projection
315        for i in 0..self.output_params.len() {
316            let qubit = QubitId(i as u32);
317            gates.push(Box::new(ParametricRotationY {
318                target: qubit,
319                theta: crate::parametric::Parameter::Constant(self.output_params[i].value),
320            }));
321        }
322
323        Ok(gates)
324    }
325}
326
327impl QMLLayer for QuantumAttention {
328    fn num_qubits(&self) -> usize {
329        self.num_qubits
330    }
331
332    fn parameters(&self) -> &[Parameter] {
333        // This would need a flattened view of all parameters
334        unimplemented!("Use all_parameters() method instead")
335    }
336
337    fn parameters_mut(&mut self) -> &mut [Parameter] {
338        unimplemented!("Use all_parameters_mut() method instead")
339    }
340
341    fn gates(&self) -> Vec<Box<dyn GateOp>> {
342        self.attention_gates().unwrap_or_default()
343    }
344
345    fn compute_gradients(
346        &self,
347        _state: &Array1<Complex64>,
348        _loss_gradient: &Array1<Complex64>,
349    ) -> QuantRS2Result<Vec<f64>> {
350        let total_params = self.query_params.len()
351            + self.key_params.len()
352            + self.value_params.len()
353            + self.output_params.len();
354        Ok(vec![0.0; total_params])
355    }
356
357    fn name(&self) -> &str {
358        "QuantumAttention"
359    }
360}
361
362/// Quantum text classifier for sentiment analysis and text classification
363pub struct QuantumTextClassifier {
364    /// Configuration
365    config: QNLPConfig,
366    /// Word embedding layer
367    embedding: QuantumWordEmbedding,
368    /// Attention layers
369    attention_layers: Vec<QuantumAttention>,
370    /// Classification parameters
371    classifier_params: Vec<Parameter>,
372    /// Number of output classes
373    num_classes: usize,
374}
375
376impl QuantumTextClassifier {
377    /// Create a new quantum text classifier
378    pub fn new(config: QNLPConfig, num_classes: usize) -> Self {
379        let embedding = QuantumWordEmbedding::new(config.clone());
380
381        // Create multiple attention layers for deeper models
382        let mut attention_layers = Vec::new();
383        for _layer_idx in 0..2 {
384            // 2 attention layers
385            attention_layers.push(QuantumAttention::new(
386                config.text_qubits,
387                config.num_attention_heads,
388            ));
389        }
390
391        // Create classification parameters
392        let mut classifier_params = Vec::new();
393        for class in 0..num_classes {
394            for qubit in 0..config.feature_qubits {
395                classifier_params.push(Parameter {
396                    name: format!("classifier_{}_{}", class, qubit),
397                    value: ((class + qubit) as f64 * 0.2).sin() * 0.3,
398                    bounds: None,
399                });
400            }
401        }
402
403        Self {
404            config,
405            embedding,
406            attention_layers,
407            classifier_params,
408            num_classes,
409        }
410    }
411
412    /// Classify a text sequence
413    pub fn classify(&self, word_ids: &[usize]) -> QuantRS2Result<Vec<f64>> {
414        // This would implement the full forward pass
415        // For now, return dummy probabilities
416        let mut probs = vec![1.0 / self.num_classes as f64; self.num_classes];
417
418        // Add some variation based on input
419        for (i, &word_id) in word_ids.iter().enumerate() {
420            let variation = ((word_id + i) as f64 * 0.1).sin() * 0.1;
421            probs[i % self.num_classes] += variation;
422        }
423
424        // Normalize probabilities
425        let sum: f64 = probs.iter().sum();
426        if sum > 0.0 {
427            for prob in &mut probs {
428                *prob /= sum;
429            }
430        }
431
432        Ok(probs)
433    }
434
435    /// Generate the full circuit for text classification
436    pub fn build_circuit(&self, word_ids: &[usize]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
437        let mut gates = Vec::new();
438
439        // 1. Word embedding
440        gates.extend(self.embedding.encode_sequence(word_ids)?);
441
442        // 2. Attention layers
443        for attention in &self.attention_layers {
444            gates.extend(attention.attention_gates()?);
445        }
446
447        // 3. Feature extraction and pooling (using measurement-like operations)
448        // This would include global pooling operations
449        for qubit in 0..self.config.text_qubits {
450            gates.push(Box::new(Hadamard {
451                target: QubitId(qubit as u32),
452            }));
453        }
454
455        // 4. Classification layer
456        for (_class, chunk) in self
457            .classifier_params
458            .chunks(self.config.feature_qubits)
459            .enumerate()
460        {
461            for (i, param) in chunk.iter().enumerate() {
462                let qubit = QubitId(i as u32);
463                gates.push(Box::new(ParametricRotationY {
464                    target: qubit,
465                    theta: crate::parametric::Parameter::Constant(param.value),
466                }));
467            }
468        }
469
470        Ok(gates)
471    }
472
473    /// Train the classifier using a dataset
474    pub fn train(
475        &mut self,
476        training_data: &[(Vec<usize>, usize)],
477        learning_rate: f64,
478        epochs: usize,
479    ) -> QuantRS2Result<Vec<f64>> {
480        let mut losses = Vec::new();
481
482        for epoch in 0..epochs {
483            let mut epoch_loss = 0.0;
484
485            for (word_ids, true_label) in training_data {
486                // Forward pass
487                let predictions = self.classify(word_ids)?;
488
489                // Compute loss (cross-entropy)
490                let loss = -predictions[*true_label].ln();
491                epoch_loss += loss;
492
493                // Backward pass (simplified gradient computation)
494                // In practice, this would use automatic differentiation
495                self.update_parameters(predictions, *true_label, learning_rate)?;
496            }
497
498            epoch_loss /= training_data.len() as f64;
499            losses.push(epoch_loss);
500
501            if epoch % 10 == 0 {
502                println!("Epoch {}: Loss = {:.4}", epoch, epoch_loss);
503            }
504        }
505
506        Ok(losses)
507    }
508
509    /// Update parameters based on gradients (simplified)
510    fn update_parameters(
511        &mut self,
512        predictions: Vec<f64>,
513        true_label: usize,
514        learning_rate: f64,
515    ) -> QuantRS2Result<()> {
516        // Simplified parameter update
517        // In practice, would compute proper gradients using parameter shift rule
518
519        for (i, param) in self.classifier_params.iter_mut().enumerate() {
520            // All parameters are learnable in this simplified implementation
521            {
522                let class_idx = i / self.config.feature_qubits;
523                let error = if class_idx == true_label {
524                    predictions[class_idx] - 1.0
525                } else {
526                    predictions[class_idx]
527                };
528
529                // Simple gradient descent update
530                param.value -= learning_rate * error * 0.1;
531            }
532        }
533
534        Ok(())
535    }
536}
537
538/// Quantum language model for text generation
539pub struct QuantumLanguageModel {
540    /// Configuration
541    config: QNLPConfig,
542    /// Embedding layer
543    embedding: QuantumWordEmbedding,
544    /// Transformer layers
545    transformer_layers: Vec<QuantumAttention>,
546    /// Output parameters
547    output_params: Vec<Parameter>,
548}
549
550impl QuantumLanguageModel {
551    /// Create a new quantum language model
552    pub fn new(config: QNLPConfig) -> Self {
553        let embedding = QuantumWordEmbedding::new(config.clone());
554
555        // Create transformer layers
556        let mut transformer_layers = Vec::new();
557        for _layer in 0..3 {
558            // 3 transformer layers
559            transformer_layers.push(QuantumAttention::new(
560                config.text_qubits,
561                config.num_attention_heads,
562            ));
563        }
564
565        // Create output parameters for next token prediction
566        let mut output_params = Vec::new();
567        for token in 0..config.vocab_size {
568            output_params.push(Parameter {
569                name: format!("output_{}", token),
570                value: (token as f64 * 0.01).sin() * 0.1,
571                bounds: None,
572            });
573        }
574
575        Self {
576            config,
577            embedding,
578            transformer_layers,
579            output_params,
580        }
581    }
582
583    /// Generate next token probabilities given a context
584    pub fn predict_next_token(&self, context: &[usize]) -> QuantRS2Result<Vec<f64>> {
585        // Build circuit for the context
586        let _gates = self.build_circuit(context)?;
587
588        // Simulate the circuit (placeholder)
589        // In practice, would run the quantum circuit and measure
590
591        // Return dummy probabilities for now
592        let mut probs = vec![1.0 / self.config.vocab_size as f64; self.config.vocab_size];
593
594        // Add some variation based on context
595        for (i, &token) in context.iter().enumerate() {
596            let variation = ((token + i) as f64 * 0.05).sin() * 0.01;
597            probs[token % self.config.vocab_size] += variation;
598        }
599
600        // Normalize
601        let sum: f64 = probs.iter().sum();
602        if sum > 0.0 {
603            for prob in &mut probs {
604                *prob /= sum;
605            }
606        }
607
608        Ok(probs)
609    }
610
611    /// Generate text given a starting context
612    pub fn generate_text(
613        &self,
614        start_context: &[usize],
615        max_length: usize,
616        temperature: f64,
617    ) -> QuantRS2Result<Vec<usize>> {
618        let mut generated = start_context.to_vec();
619
620        for _step in 0..max_length {
621            // Get context (last N tokens)
622            let context_start = if generated.len() > self.config.max_sequence_length {
623                generated.len() - self.config.max_sequence_length
624            } else {
625                0
626            };
627            let context = &generated[context_start..];
628
629            // Predict next token
630            let mut probs = self.predict_next_token(context)?;
631
632            // Apply temperature scaling
633            if temperature != 1.0 {
634                for prob in &mut probs {
635                    *prob = (*prob).powf(1.0 / temperature);
636                }
637                let sum: f64 = probs.iter().sum();
638                for prob in &mut probs {
639                    *prob /= sum;
640                }
641            }
642
643            // Sample next token (using simple deterministic selection for now)
644            let next_token = probs
645                .iter()
646                .enumerate()
647                .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
648                .map(|(i, _)| i)
649                .unwrap();
650
651            generated.push(next_token);
652        }
653
654        Ok(generated)
655    }
656
657    /// Build the full language model circuit
658    fn build_circuit(&self, context: &[usize]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
659        let mut gates = Vec::new();
660
661        // 1. Embedding
662        gates.extend(self.embedding.encode_sequence(context)?);
663
664        // 2. Transformer layers
665        for transformer in &self.transformer_layers {
666            gates.extend(transformer.attention_gates()?);
667        }
668
669        // 3. Output projection
670        for (i, param) in self.output_params.iter().enumerate() {
671            let qubit = QubitId((i % self.config.text_qubits) as u32);
672            gates.push(Box::new(ParametricRotationZ {
673                target: qubit,
674                theta: crate::parametric::Parameter::Constant(param.value),
675            }));
676        }
677
678        Ok(gates)
679    }
680}
681
682#[cfg(test)]
683mod tests {
684    use super::*;
685
686    #[test]
687    fn test_quantum_word_embedding() {
688        let config = QNLPConfig {
689            vocab_size: 100,
690            text_qubits: 4,
691            ..Default::default()
692        };
693
694        let embedding = QuantumWordEmbedding::new(config);
695        assert_eq!(embedding.num_qubits(), 4);
696
697        // Test encoding a simple sequence
698        let word_ids = vec![1, 5, 10];
699        let gates = embedding.encode_sequence(&word_ids).unwrap();
700        assert!(!gates.is_empty());
701    }
702
703    #[test]
704    fn test_quantum_attention() {
705        let attention = QuantumAttention::new(8, 2);
706        assert_eq!(attention.num_qubits(), 8);
707        assert_eq!(attention.num_heads, 2);
708
709        let gates = attention.attention_gates().unwrap();
710        assert!(!gates.is_empty());
711    }
712
713    #[test]
714    fn test_quantum_text_classifier() {
715        let config = QNLPConfig {
716            vocab_size: 50,
717            text_qubits: 4,
718            feature_qubits: 2,
719            ..Default::default()
720        };
721
722        let classifier = QuantumTextClassifier::new(config, 3);
723
724        // Test classification
725        let word_ids = vec![1, 2, 3];
726        let probs = classifier.classify(&word_ids).unwrap();
727        assert_eq!(probs.len(), 3);
728
729        // Check probabilities sum to 1
730        let sum: f64 = probs.iter().sum();
731        assert!((sum - 1.0).abs() < 1e-10);
732    }
733
734    #[test]
735    fn test_quantum_language_model() {
736        let config = QNLPConfig {
737            vocab_size: 20,
738            text_qubits: 4,
739            max_sequence_length: 8,
740            ..Default::default()
741        };
742
743        let lm = QuantumLanguageModel::new(config);
744
745        // Test next token prediction
746        let context = vec![1, 2, 3];
747        let probs = lm.predict_next_token(&context).unwrap();
748        assert_eq!(probs.len(), 20);
749
750        // Test text generation
751        let generated = lm.generate_text(&context, 5, 1.0).unwrap();
752        assert_eq!(generated.len(), 8); // 3 context + 5 generated
753    }
754
755    #[test]
756    fn test_text_classifier_training() {
757        let config = QNLPConfig {
758            vocab_size: 10,
759            text_qubits: 3,
760            feature_qubits: 2,
761            ..Default::default()
762        };
763
764        let mut classifier = QuantumTextClassifier::new(config, 2);
765
766        // Create dummy training data
767        let training_data = vec![
768            (vec![1, 2], 0), // Class 0
769            (vec![3, 4], 1), // Class 1
770            (vec![1, 3], 0), // Class 0
771            (vec![2, 4], 1), // Class 1
772        ];
773
774        let losses = classifier.train(&training_data, 0.01, 5).unwrap();
775        assert_eq!(losses.len(), 5);
776    }
777}
778
779/// Advanced quantum NLP utilities and algorithms
780pub mod advanced {
781    use super::*;
782
783    /// Quantum text preprocessing utilities
784    pub struct QuantumTextPreprocessor {
785        /// Vocabulary mapping
786        vocab: HashMap<String, usize>,
787        /// Reverse vocabulary mapping
788        reverse_vocab: HashMap<usize, String>,
789        /// Special tokens
790        special_tokens: HashMap<String, usize>,
791    }
792
793    impl QuantumTextPreprocessor {
794        /// Create a new preprocessor
795        pub fn new() -> Self {
796            let mut special_tokens = HashMap::new();
797            special_tokens.insert("<PAD>".to_string(), 0);
798            special_tokens.insert("<UNK>".to_string(), 1);
799            special_tokens.insert("<START>".to_string(), 2);
800            special_tokens.insert("<END>".to_string(), 3);
801
802            Self {
803                vocab: HashMap::new(),
804                reverse_vocab: HashMap::new(),
805                special_tokens,
806            }
807        }
808
809        /// Build vocabulary from text corpus
810        pub fn build_vocab(&mut self, texts: &[String], max_vocab_size: usize) {
811            let mut word_counts: HashMap<String, usize> = HashMap::new();
812
813            // Count word frequencies
814            for text in texts {
815                for word in text.split_whitespace() {
816                    *word_counts.entry(word.to_lowercase()).or_insert(0) += 1;
817                }
818            }
819
820            // Sort by frequency and take top words
821            let mut word_freq: Vec<_> = word_counts.into_iter().collect();
822            word_freq.sort_by(|a, b| b.1.cmp(&a.1));
823
824            // Add special tokens first
825            for (token, id) in &self.special_tokens {
826                self.vocab.insert(token.clone(), *id);
827                self.reverse_vocab.insert(*id, token.clone());
828            }
829
830            // Add most frequent words
831            let mut vocab_id = self.special_tokens.len();
832            for (word, _count) in word_freq
833                .into_iter()
834                .take(max_vocab_size - self.special_tokens.len())
835            {
836                self.vocab.insert(word.clone(), vocab_id);
837                self.reverse_vocab.insert(vocab_id, word);
838                vocab_id += 1;
839            }
840        }
841
842        /// Tokenize text to word IDs
843        pub fn tokenize(&self, text: &str) -> Vec<usize> {
844            let mut tokens = vec![self.special_tokens["<START>"]];
845
846            for word in text.split_whitespace() {
847                let word = word.to_lowercase();
848                let token_id = self
849                    .vocab
850                    .get(&word)
851                    .copied()
852                    .unwrap_or(self.special_tokens["<UNK>"]);
853                tokens.push(token_id);
854            }
855
856            tokens.push(self.special_tokens["<END>"]);
857            tokens
858        }
859
860        /// Convert token IDs back to text
861        pub fn detokenize(&self, token_ids: &[usize]) -> String {
862            token_ids
863                .iter()
864                .filter_map(|&id| self.reverse_vocab.get(&id))
865                .filter(|&word| !["<PAD>", "<START>", "<END>"].contains(&word.as_str()))
866                .cloned()
867                .collect::<Vec<_>>()
868                .join(" ")
869        }
870
871        /// Get vocabulary size
872        pub fn vocab_size(&self) -> usize {
873            self.vocab.len()
874        }
875    }
876
877    /// Quantum semantic similarity computation
878    pub struct QuantumSemanticSimilarity {
879        /// Embedding dimension
880        embedding_dim: usize,
881        /// Number of qubits
882        num_qubits: usize,
883        /// Similarity computation parameters
884        similarity_params: Vec<Parameter>,
885    }
886
887    impl QuantumSemanticSimilarity {
888        /// Create a new quantum semantic similarity computer
889        pub fn new(embedding_dim: usize, num_qubits: usize) -> Self {
890            let mut similarity_params = Vec::new();
891
892            // Parameters for similarity computation
893            for i in 0..num_qubits * 2 {
894                // For two text inputs
895                similarity_params.push(Parameter {
896                    name: format!("sim_{}", i),
897                    value: (i as f64 * 0.1).sin() * 0.5,
898                    bounds: None,
899                });
900            }
901
902            Self {
903                embedding_dim,
904                num_qubits,
905                similarity_params,
906            }
907        }
908
909        /// Compute semantic similarity between two texts
910        pub fn compute_similarity(
911            &self,
912            text1_tokens: &[usize],
913            text2_tokens: &[usize],
914        ) -> QuantRS2Result<f64> {
915            // Create embeddings for both texts
916            let config = QNLPConfig {
917                text_qubits: self.num_qubits,
918                vocab_size: 1000, // Default
919                ..Default::default()
920            };
921
922            let embedding1 = QuantumWordEmbedding::new(config.clone());
923            let embedding2 = QuantumWordEmbedding::new(config);
924
925            // Generate quantum circuits for both texts
926            let gates1 = embedding1.encode_sequence(text1_tokens)?;
927            let gates2 = embedding2.encode_sequence(text2_tokens)?;
928
929            // Compute similarity using quantum interference
930            // This is a simplified version - full implementation would measure overlap
931            let similarity = self.quantum_text_overlap(gates1, gates2)?;
932
933            Ok(similarity)
934        }
935
936        /// Compute quantum overlap between two text representations
937        fn quantum_text_overlap(
938            &self,
939            _gates1: Vec<Box<dyn GateOp>>,
940            _gates2: Vec<Box<dyn GateOp>>,
941        ) -> QuantRS2Result<f64> {
942            // Placeholder for quantum overlap computation
943            // In practice, would:
944            // 1. Prepare states using gates1 and gates2
945            // 2. Compute fidelity/overlap between states
946            // 3. Return similarity score
947
948            // Return dummy similarity for now
949            Ok(0.7)
950        }
951    }
952
953    /// Quantum text summarization model
954    pub struct QuantumTextSummarizer {
955        /// Configuration
956        config: QNLPConfig,
957        /// Encoder for input text
958        encoder: QuantumWordEmbedding,
959        /// Attention mechanism for importance scoring
960        attention: QuantumAttention,
961        /// Summary generation parameters
962        summary_params: Vec<Parameter>,
963    }
964
965    impl QuantumTextSummarizer {
966        /// Create a new quantum text summarizer
967        pub fn new(config: QNLPConfig) -> Self {
968            let encoder = QuantumWordEmbedding::new(config.clone());
969            let attention = QuantumAttention::new(config.text_qubits, config.num_attention_heads);
970
971            let mut summary_params = Vec::new();
972            for i in 0..config.text_qubits {
973                summary_params.push(Parameter {
974                    name: format!("summary_{}", i),
975                    value: (i as f64 * 0.15).sin() * 0.4,
976                    bounds: None,
977                });
978            }
979
980            Self {
981                config,
982                encoder,
983                attention,
984                summary_params,
985            }
986        }
987
988        /// Generate extractive summary from input text
989        pub fn extractive_summarize(
990            &self,
991            text_tokens: &[usize],
992            summary_length: usize,
993        ) -> QuantRS2Result<Vec<usize>> {
994            // Encode input text
995            let _encoding_gates = self.encoder.encode_sequence(text_tokens)?;
996
997            // Apply attention to find important tokens
998            let _attention_gates = self.attention.attention_gates()?;
999
1000            // Score tokens for importance (simplified)
1001            let mut token_scores = Vec::new();
1002            for (i, &token) in text_tokens.iter().enumerate() {
1003                // Simple scoring based on token frequency and position
1004                let position_weight = 1.0 - (i as f64 / text_tokens.len() as f64) * 0.5;
1005                let token_weight = (token as f64 * 0.1).sin().abs();
1006                let score = position_weight * token_weight;
1007                token_scores.push((i, token, score));
1008            }
1009
1010            // Sort by score and select top tokens
1011            token_scores.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap());
1012
1013            let mut summary_tokens = Vec::new();
1014            for (_, token, _) in token_scores.into_iter().take(summary_length) {
1015                summary_tokens.push(token);
1016            }
1017
1018            Ok(summary_tokens)
1019        }
1020
1021        /// Generate abstractive summary (placeholder)
1022        pub fn abstractive_summarize(
1023            &self,
1024            _text_tokens: &[usize],
1025            _summary_length: usize,
1026        ) -> QuantRS2Result<Vec<usize>> {
1027            // Placeholder for abstractive summarization
1028            // Would use sequence-to-sequence quantum model
1029            Ok(vec![1, 2, 3]) // Dummy summary
1030        }
1031    }
1032
1033    /// Quantum named entity recognition
1034    pub struct QuantumNamedEntityRecognition {
1035        /// Configuration
1036        config: QNLPConfig,
1037        /// Token encoder
1038        encoder: QuantumWordEmbedding,
1039        /// Entity type classifiers
1040        entity_classifiers: HashMap<String, Vec<Parameter>>,
1041        /// Supported entity types
1042        entity_types: Vec<String>,
1043    }
1044
1045    impl QuantumNamedEntityRecognition {
1046        /// Create a new quantum NER model
1047        pub fn new(config: QNLPConfig) -> Self {
1048            let encoder = QuantumWordEmbedding::new(config.clone());
1049            let entity_types = vec![
1050                "PERSON".to_string(),
1051                "ORGANIZATION".to_string(),
1052                "LOCATION".to_string(),
1053                "DATE".to_string(),
1054                "MONEY".to_string(),
1055            ];
1056
1057            let mut entity_classifiers = HashMap::new();
1058            for entity_type in &entity_types {
1059                let mut classifier_params = Vec::new();
1060                for i in 0..config.text_qubits {
1061                    classifier_params.push(Parameter {
1062                        name: format!("{}_{}", entity_type, i),
1063                        value: (entity_type.len() as f64 + i as f64 * 0.1).sin() * 0.3,
1064                        bounds: None,
1065                    });
1066                }
1067                entity_classifiers.insert(entity_type.clone(), classifier_params);
1068            }
1069
1070            Self {
1071                config,
1072                encoder,
1073                entity_classifiers,
1074                entity_types,
1075            }
1076        }
1077
1078        /// Recognize named entities in text
1079        pub fn recognize_entities(
1080            &self,
1081            text_tokens: &[usize],
1082        ) -> QuantRS2Result<Vec<(usize, usize, String)>> {
1083            let mut entities = Vec::new();
1084
1085            // Simple sliding window approach
1086            for start in 0..text_tokens.len() {
1087                for end in start + 1..=text_tokens.len().min(start + 5) {
1088                    // Max entity length 5
1089                    let entity_tokens = &text_tokens[start..end];
1090
1091                    // Classify this span
1092                    if let Some(entity_type) = self.classify_span(entity_tokens)? {
1093                        entities.push((start, end, entity_type));
1094                    }
1095                }
1096            }
1097
1098            // Remove overlapping entities (keep longer ones)
1099            entities.sort_by(|a, b| (b.1 - b.0).cmp(&(a.1 - a.0)));
1100            let mut final_entities = Vec::new();
1101            let mut used_positions = vec![false; text_tokens.len()];
1102
1103            for (start, end, entity_type) in entities {
1104                if used_positions[start..end].iter().all(|&used| !used) {
1105                    for pos in start..end {
1106                        used_positions[pos] = true;
1107                    }
1108                    final_entities.push((start, end, entity_type));
1109                }
1110            }
1111
1112            final_entities.sort_by_key(|&(start, _, _)| start);
1113            Ok(final_entities)
1114        }
1115
1116        /// Classify a span of tokens as an entity type
1117        fn classify_span(&self, tokens: &[usize]) -> QuantRS2Result<Option<String>> {
1118            // Encode the span
1119            let _encoding_gates = self.encoder.encode_sequence(tokens)?;
1120
1121            let mut best_score = 0.0;
1122            let mut best_type = None;
1123
1124            // Score each entity type
1125            for entity_type in &self.entity_types {
1126                let score = self.compute_entity_score(tokens, entity_type)?;
1127                if score > best_score && score > 0.5 {
1128                    // Threshold
1129                    best_score = score;
1130                    best_type = Some(entity_type.clone());
1131                }
1132            }
1133
1134            Ok(best_type)
1135        }
1136
1137        /// Compute score for a specific entity type
1138        fn compute_entity_score(&self, tokens: &[usize], entity_type: &str) -> QuantRS2Result<f64> {
1139            // Simple scoring based on token patterns
1140            let mut score = 0.0;
1141
1142            for &token in tokens {
1143                // Simple heuristics based on token ID patterns
1144                match entity_type {
1145                    "PERSON" => {
1146                        if token % 7 == 1 {
1147                            // Arbitrary pattern for person names
1148                            score += 0.3;
1149                        }
1150                    }
1151                    "LOCATION" => {
1152                        if token % 5 == 2 {
1153                            // Arbitrary pattern for locations
1154                            score += 0.3;
1155                        }
1156                    }
1157                    "ORGANIZATION" => {
1158                        if token % 11 == 3 {
1159                            // Arbitrary pattern for organizations
1160                            score += 0.3;
1161                        }
1162                    }
1163                    "DATE" => {
1164                        if token % 13 == 4 {
1165                            // Arbitrary pattern for dates
1166                            score += 0.3;
1167                        }
1168                    }
1169                    "MONEY" => {
1170                        if token % 17 == 5 {
1171                            // Arbitrary pattern for money
1172                            score += 0.3;
1173                        }
1174                    }
1175                    _ => {}
1176                }
1177            }
1178
1179            score /= tokens.len() as f64; // Normalize by span length
1180            Ok(score)
1181        }
1182    }
1183}
1184
1185// Re-export advanced utilities
1186pub use advanced::*;