1use super::{Parameter, QMLLayer};
8use crate::{
9 error::{QuantRS2Error, QuantRS2Result},
10 gate::{multi::*, single::*, GateOp},
11 parametric::{ParametricRotationX, ParametricRotationY, ParametricRotationZ},
12 qubit::QubitId,
13};
14use scirs2_core::ndarray::Array1;
15use scirs2_core::Complex64;
16use std::collections::HashMap;
17use std::f64::consts::PI;
18
19fn circuit_num_qubits(gates: &[Box<dyn GateOp>], min_qubits: usize) -> usize {
25 let max_index = gates
26 .iter()
27 .flat_map(|gate| gate.qubits())
28 .map(|q| q.0 as usize)
29 .max();
30 match max_index {
31 Some(idx) => min_qubits.max(idx + 1),
32 None => min_qubits.max(1),
33 }
34}
35
36fn readout_bits(count: usize) -> usize {
39 if count <= 1 {
40 1
41 } else {
42 (usize::BITS - (count - 1).leading_zeros()) as usize
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum TextEmbeddingStrategy {
49 WordLevel,
51 CharLevel,
53 NGram(usize),
55 TokenPositional,
57 Hierarchical,
59}
60
61#[derive(Debug, Clone)]
63pub struct QNLPConfig {
64 pub text_qubits: usize,
66 pub feature_qubits: usize,
68 pub max_sequence_length: usize,
70 pub vocab_size: usize,
72 pub embedding_dim: usize,
74 pub embedding_strategy: TextEmbeddingStrategy,
76 pub num_attention_heads: usize,
78 pub hidden_dim: usize,
80}
81
82impl Default for QNLPConfig {
83 fn default() -> Self {
84 Self {
85 text_qubits: 8,
86 feature_qubits: 4,
87 max_sequence_length: 32,
88 vocab_size: 1000,
89 embedding_dim: 64,
90 embedding_strategy: TextEmbeddingStrategy::WordLevel,
91 num_attention_heads: 4,
92 hidden_dim: 128,
93 }
94 }
95}
96
97pub struct QuantumWordEmbedding {
99 config: QNLPConfig,
101 embeddings: Vec<Vec<Parameter>>,
103 flat_params: Vec<Parameter>,
107 num_qubits: usize,
109}
110
111impl QuantumWordEmbedding {
112 pub fn new(config: QNLPConfig) -> Self {
114 let num_qubits = config.text_qubits;
115 let mut embeddings = Vec::new();
116 let mut flat_params: Vec<Parameter> = Vec::new();
117
118 for word_id in 0..config.vocab_size {
120 let mut word_embedding = Vec::new();
121 for qubit in 0..num_qubits {
122 let value = ((word_id * qubit.max(1)) as f64 * 0.1).sin() * 0.5;
124 let param = Parameter {
125 name: format!("embed_{word_id}_{qubit}"),
126 value,
127 bounds: None,
128 };
129 flat_params.push(param.clone());
130 word_embedding.push(param);
131 }
132 embeddings.push(word_embedding);
133 }
134
135 Self {
136 config,
137 embeddings,
138 flat_params,
139 num_qubits,
140 }
141 }
142
143 fn rebuild_flat_cache(&mut self) {
145 self.flat_params.clear();
146 for word_emb in &self.embeddings {
147 self.flat_params.extend(word_emb.iter().cloned());
148 }
149 }
150
151 fn sync_from_flat(&mut self) {
154 let nq = self.num_qubits;
155 for (word_id, word_emb) in self.embeddings.iter_mut().enumerate() {
156 for (qubit, param) in word_emb.iter_mut().enumerate() {
157 let flat_idx = word_id * nq + qubit;
158 if let Some(flat_param) = self.flat_params.get(flat_idx) {
159 param.value = flat_param.value;
160 }
161 }
162 }
163 }
164
165 pub fn encode_sequence(&self, word_ids: &[usize]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
167 let mut gates: Vec<Box<dyn GateOp>> = Vec::new();
168 let nq = self.num_qubits;
169
170 for (position, &word_id) in word_ids.iter().enumerate() {
171 if word_id >= self.config.vocab_size {
172 return Err(QuantRS2Error::InvalidInput(format!(
173 "Word ID {} exceeds vocabulary size {}",
174 word_id, self.config.vocab_size
175 )));
176 }
177
178 if position >= self.config.max_sequence_length {
179 break; }
181
182 let flat_base = word_id * nq;
184 for qubit_idx in 0..nq {
185 let flat_idx = flat_base + qubit_idx;
186 let value = self
187 .flat_params
188 .get(flat_idx)
189 .map(|p| p.value)
190 .unwrap_or(0.0);
191
192 let qubit = QubitId(qubit_idx as u32);
193
194 gates.push(Box::new(ParametricRotationY {
196 target: qubit,
197 theta: crate::parametric::Parameter::Constant(value * PI),
198 }));
199
200 let positional_angle =
202 (position as f64) / (self.config.max_sequence_length as f64) * PI;
203 gates.push(Box::new(ParametricRotationZ {
204 target: qubit,
205 theta: crate::parametric::Parameter::Constant(positional_angle * 0.1),
206 }));
207 }
208 }
209
210 Ok(gates)
211 }
212}
213
214impl QMLLayer for QuantumWordEmbedding {
215 fn num_qubits(&self) -> usize {
216 self.num_qubits
217 }
218
219 fn parameters(&self) -> &[Parameter] {
220 &self.flat_params
224 }
225
226 fn parameters_mut(&mut self) -> &mut [Parameter] {
227 &mut self.flat_params
233 }
234
235 fn gates(&self) -> Vec<Box<dyn GateOp>> {
236 Vec::new()
238 }
239
240 fn compute_gradients(
241 &self,
242 _state: &Array1<Complex64>,
243 _loss_gradient: &Array1<Complex64>,
244 ) -> QuantRS2Result<Vec<f64>> {
245 let total_params = self.config.vocab_size * self.num_qubits;
247 Ok(vec![0.0; total_params])
248 }
249
250 fn name(&self) -> &'static str {
251 "QuantumWordEmbedding"
252 }
253}
254
255pub struct QuantumAttention {
257 num_qubits: usize,
259 num_heads: usize,
261 query_params: Vec<Parameter>,
263 key_params: Vec<Parameter>,
265 value_params: Vec<Parameter>,
267 output_params: Vec<Parameter>,
269 flat_params: Vec<Parameter>,
272}
273
274impl QuantumAttention {
275 pub fn new(num_qubits: usize, num_heads: usize) -> Self {
277 let params_per_head = num_qubits / num_heads.max(1);
278
279 let mut query_params = Vec::new();
280 let mut key_params = Vec::new();
281 let mut value_params = Vec::new();
282 let mut output_params = Vec::new();
283
284 for head in 0..num_heads {
286 for i in 0..params_per_head {
287 query_params.push(Parameter {
289 name: format!("query_{head}_{i}"),
290 value: ((head + i) as f64 * 0.1).sin() * 0.5,
291 bounds: None,
292 });
293
294 key_params.push(Parameter {
296 name: format!("key_{head}_{i}"),
297 value: ((head + i + 1) as f64 * 0.1).cos() * 0.5,
298 bounds: None,
299 });
300
301 value_params.push(Parameter {
303 name: format!("value_{head}_{i}"),
304 value: ((head + i + 2) as f64 * 0.1).sin() * 0.5,
305 bounds: None,
306 });
307
308 output_params.push(Parameter {
310 name: format!("output_{head}_{i}"),
311 value: ((head + i + 3) as f64 * 0.1).cos() * 0.5,
312 bounds: None,
313 });
314 }
315 }
316
317 let mut flat_params: Vec<Parameter> = Vec::new();
319 flat_params.extend(query_params.iter().cloned());
320 flat_params.extend(key_params.iter().cloned());
321 flat_params.extend(value_params.iter().cloned());
322 flat_params.extend(output_params.iter().cloned());
323
324 Self {
325 num_qubits,
326 num_heads,
327 query_params,
328 key_params,
329 value_params,
330 output_params,
331 flat_params,
332 }
333 }
334
335 pub fn rebuild_flat_cache(&mut self) {
337 self.flat_params.clear();
338 self.flat_params.extend(self.query_params.iter().cloned());
339 self.flat_params.extend(self.key_params.iter().cloned());
340 self.flat_params.extend(self.value_params.iter().cloned());
341 self.flat_params.extend(self.output_params.iter().cloned());
342 }
343
344 pub fn sync_from_flat(&mut self) {
346 let qlen = self.query_params.len();
347 let klen = self.key_params.len();
348 let vlen = self.value_params.len();
349
350 for (i, p) in self.query_params.iter_mut().enumerate() {
351 if let Some(fp) = self.flat_params.get(i) {
352 p.value = fp.value;
353 }
354 }
355 for (i, p) in self.key_params.iter_mut().enumerate() {
356 if let Some(fp) = self.flat_params.get(qlen + i) {
357 p.value = fp.value;
358 }
359 }
360 for (i, p) in self.value_params.iter_mut().enumerate() {
361 if let Some(fp) = self.flat_params.get(qlen + klen + i) {
362 p.value = fp.value;
363 }
364 }
365 for (i, p) in self.output_params.iter_mut().enumerate() {
366 if let Some(fp) = self.flat_params.get(qlen + klen + vlen + i) {
367 p.value = fp.value;
368 }
369 }
370 }
371
372 pub fn attention_gates(&self) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
374 let mut gates: Vec<Box<dyn GateOp>> = Vec::new();
375 let params_per_head = self.num_qubits / self.num_heads;
376
377 for head in 0..self.num_heads {
379 let head_offset = head * params_per_head;
380
381 for i in 0..params_per_head {
383 let qubit = QubitId((head_offset + i) as u32);
384 let param_idx = head * params_per_head + i;
385
386 gates.push(Box::new(ParametricRotationY {
387 target: qubit,
388 theta: crate::parametric::Parameter::Constant(
389 self.query_params[param_idx].value,
390 ),
391 }));
392 }
393
394 for i in 0..params_per_head {
396 let qubit = QubitId((head_offset + i) as u32);
397 let param_idx = head * params_per_head + i;
398
399 gates.push(Box::new(ParametricRotationZ {
400 target: qubit,
401 theta: crate::parametric::Parameter::Constant(self.key_params[param_idx].value),
402 }));
403 }
404
405 for i in 0..params_per_head.saturating_sub(1) {
409 let control = QubitId((head_offset + i) as u32);
410 let target = QubitId((head_offset + i + 1) as u32);
411 gates.push(Box::new(CNOT { control, target }));
412 }
413
414 for i in 0..params_per_head {
416 let qubit = QubitId((head_offset + i) as u32);
417 let param_idx = head * params_per_head + i;
418
419 gates.push(Box::new(ParametricRotationX {
420 target: qubit,
421 theta: crate::parametric::Parameter::Constant(
422 self.value_params[param_idx].value,
423 ),
424 }));
425 }
426 }
427
428 if params_per_head > 0 {
432 for head in 0..self.num_heads.saturating_sub(1) {
433 let control = QubitId((head * params_per_head) as u32);
434 let target = QubitId(((head + 1) * params_per_head) as u32);
435 if control.0 != target.0 {
436 gates.push(Box::new(CNOT { control, target }));
437 }
438 }
439 }
440
441 for i in 0..self.output_params.len() {
443 let qubit = QubitId(i as u32);
444 gates.push(Box::new(ParametricRotationY {
445 target: qubit,
446 theta: crate::parametric::Parameter::Constant(self.output_params[i].value),
447 }));
448 }
449
450 Ok(gates)
451 }
452}
453
454impl QMLLayer for QuantumAttention {
455 fn num_qubits(&self) -> usize {
456 self.num_qubits
457 }
458
459 fn parameters(&self) -> &[Parameter] {
460 &self.flat_params
464 }
465
466 fn parameters_mut(&mut self) -> &mut [Parameter] {
467 &mut self.flat_params
471 }
472
473 fn gates(&self) -> Vec<Box<dyn GateOp>> {
474 self.attention_gates().unwrap_or_default()
475 }
476
477 fn compute_gradients(
478 &self,
479 _state: &Array1<Complex64>,
480 _loss_gradient: &Array1<Complex64>,
481 ) -> QuantRS2Result<Vec<f64>> {
482 let total_params = self.query_params.len()
483 + self.key_params.len()
484 + self.value_params.len()
485 + self.output_params.len();
486 Ok(vec![0.0; total_params])
487 }
488
489 fn name(&self) -> &'static str {
490 "QuantumAttention"
491 }
492}
493
494pub struct QuantumTextClassifier {
496 config: QNLPConfig,
498 embedding: QuantumWordEmbedding,
500 attention_layers: Vec<QuantumAttention>,
502 classifier_params: Vec<Parameter>,
504 num_classes: usize,
506}
507
508impl QuantumTextClassifier {
509 pub fn new(config: QNLPConfig, num_classes: usize) -> Self {
511 let embedding = QuantumWordEmbedding::new(config.clone());
512
513 let mut attention_layers = Vec::new();
515 for _layer_idx in 0..2 {
516 attention_layers.push(QuantumAttention::new(
518 config.text_qubits,
519 config.num_attention_heads,
520 ));
521 }
522
523 let mut classifier_params = Vec::new();
525 for class in 0..num_classes {
526 for qubit in 0..config.feature_qubits {
527 classifier_params.push(Parameter {
528 name: format!("classifier_{class}_{qubit}"),
529 value: ((class + qubit) as f64 * 0.2).sin() * 0.3,
530 bounds: None,
531 });
532 }
533 }
534
535 Self {
536 config,
537 embedding,
538 attention_layers,
539 classifier_params,
540 num_classes,
541 }
542 }
543
544 pub fn classify(&self, word_ids: &[usize]) -> QuantRS2Result<Vec<f64>> {
552 let gates = self.build_circuit(word_ids)?;
553 let num_qubits = circuit_num_qubits(&gates, self.config.text_qubits);
554 let state = crate::qml::simulator::simulate(num_qubits, &gates)?;
555 let amplitudes = crate::qml::simulator::probabilities(&state);
556
557 let class_bits = readout_bits(self.num_classes);
559 let class_mask = (1usize << class_bits) - 1;
560
561 let mut probs = vec![0.0; self.num_classes];
562 for (basis_index, prob) in amplitudes.iter().enumerate() {
563 let class = basis_index & class_mask;
564 if class < self.num_classes {
565 probs[class] += prob;
566 }
567 }
568
569 let sum: f64 = probs.iter().sum();
571 if sum > 0.0 {
572 for prob in &mut probs {
573 *prob /= sum;
574 }
575 } else {
576 let uniform = 1.0 / self.num_classes as f64;
579 probs.fill(uniform);
580 }
581
582 Ok(probs)
583 }
584
585 pub fn build_circuit(&self, word_ids: &[usize]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
587 let mut gates = Vec::new();
588
589 gates.extend(self.embedding.encode_sequence(word_ids)?);
591
592 for attention in &self.attention_layers {
594 gates.extend(attention.attention_gates()?);
595 }
596
597 for qubit in 0..self.config.text_qubits {
600 gates.push(Box::new(Hadamard {
601 target: QubitId(qubit as u32),
602 }));
603 }
604
605 for (_class, chunk) in self
607 .classifier_params
608 .chunks(self.config.feature_qubits)
609 .enumerate()
610 {
611 for (i, param) in chunk.iter().enumerate() {
612 let qubit = QubitId(i as u32);
613 gates.push(Box::new(ParametricRotationY {
614 target: qubit,
615 theta: crate::parametric::Parameter::Constant(param.value),
616 }));
617 }
618 }
619
620 Ok(gates)
621 }
622
623 pub fn train(
625 &mut self,
626 training_data: &[(Vec<usize>, usize)],
627 learning_rate: f64,
628 epochs: usize,
629 ) -> QuantRS2Result<Vec<f64>> {
630 let mut losses = Vec::new();
631
632 for epoch in 0..epochs {
633 let mut epoch_loss = 0.0;
634
635 for (word_ids, true_label) in training_data {
636 let predictions = self.classify(word_ids)?;
638
639 let loss = -predictions[*true_label].ln();
641 epoch_loss += loss;
642
643 self.update_parameters(predictions, *true_label, learning_rate)?;
646 }
647
648 epoch_loss /= training_data.len() as f64;
649 losses.push(epoch_loss);
650
651 if epoch % 10 == 0 {
652 println!("Epoch {epoch}: Loss = {epoch_loss:.4}");
653 }
654 }
655
656 Ok(losses)
657 }
658
659 fn update_parameters(
661 &mut self,
662 predictions: Vec<f64>,
663 true_label: usize,
664 learning_rate: f64,
665 ) -> QuantRS2Result<()> {
666 for (i, param) in self.classifier_params.iter_mut().enumerate() {
670 {
672 let class_idx = i / self.config.feature_qubits;
673 let error = if class_idx == true_label {
674 predictions[class_idx] - 1.0
675 } else {
676 predictions[class_idx]
677 };
678
679 param.value -= learning_rate * error * 0.1;
681 }
682 }
683
684 Ok(())
685 }
686}
687
688pub struct QuantumLanguageModel {
690 config: QNLPConfig,
692 embedding: QuantumWordEmbedding,
694 transformer_layers: Vec<QuantumAttention>,
696 output_params: Vec<Parameter>,
698}
699
700impl QuantumLanguageModel {
701 pub fn new(config: QNLPConfig) -> Self {
703 let embedding = QuantumWordEmbedding::new(config.clone());
704
705 let mut transformer_layers = Vec::new();
707 for _layer in 0..3 {
708 transformer_layers.push(QuantumAttention::new(
710 config.text_qubits,
711 config.num_attention_heads,
712 ));
713 }
714
715 let mut output_params = Vec::new();
717 for token in 0..config.vocab_size {
718 output_params.push(Parameter {
719 name: format!("output_{token}"),
720 value: (token as f64 * 0.01).sin() * 0.1,
721 bounds: None,
722 });
723 }
724
725 Self {
726 config,
727 embedding,
728 transformer_layers,
729 output_params,
730 }
731 }
732
733 pub fn predict_next_token(&self, context: &[usize]) -> QuantRS2Result<Vec<f64>> {
741 let gates = self.build_circuit(context)?;
742 let num_qubits = circuit_num_qubits(&gates, self.config.text_qubits);
743 let state = crate::qml::simulator::simulate(num_qubits, &gates)?;
744 let amplitudes = crate::qml::simulator::probabilities(&state);
745
746 let vocab = self.config.vocab_size;
747 let token_bits = readout_bits(vocab);
748 let token_mask = (1usize << token_bits) - 1;
749
750 let mut probs = vec![0.0; vocab];
751 for (basis_index, prob) in amplitudes.iter().enumerate() {
752 let token = basis_index & token_mask;
753 if token < vocab {
754 probs[token] += prob;
755 }
756 }
757
758 let sum: f64 = probs.iter().sum();
759 if sum > 0.0 {
760 for prob in &mut probs {
761 *prob /= sum;
762 }
763 } else {
764 let uniform = 1.0 / vocab as f64;
765 probs.fill(uniform);
766 }
767
768 Ok(probs)
769 }
770
771 pub fn generate_text(
773 &self,
774 start_context: &[usize],
775 max_length: usize,
776 temperature: f64,
777 ) -> QuantRS2Result<Vec<usize>> {
778 let mut generated = start_context.to_vec();
779
780 for _step in 0..max_length {
781 let context_start = if generated.len() > self.config.max_sequence_length {
783 generated.len() - self.config.max_sequence_length
784 } else {
785 0
786 };
787 let context = &generated[context_start..];
788
789 let mut probs = self.predict_next_token(context)?;
791
792 if temperature != 1.0 {
794 for prob in &mut probs {
795 *prob = (*prob).powf(1.0 / temperature);
796 }
797 let sum: f64 = probs.iter().sum();
798 for prob in &mut probs {
799 *prob /= sum;
800 }
801 }
802
803 let next_token = probs
805 .iter()
806 .enumerate()
807 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
808 .map(|(i, _)| i)
809 .unwrap_or(0);
810
811 generated.push(next_token);
812 }
813
814 Ok(generated)
815 }
816
817 fn build_circuit(&self, context: &[usize]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
819 let mut gates = Vec::new();
820
821 gates.extend(self.embedding.encode_sequence(context)?);
823
824 for transformer in &self.transformer_layers {
826 gates.extend(transformer.attention_gates()?);
827 }
828
829 for (i, param) in self.output_params.iter().enumerate() {
831 let qubit = QubitId((i % self.config.text_qubits) as u32);
832 gates.push(Box::new(ParametricRotationZ {
833 target: qubit,
834 theta: crate::parametric::Parameter::Constant(param.value),
835 }));
836 }
837
838 Ok(gates)
839 }
840}
841
842#[cfg(test)]
843mod tests {
844 use super::*;
845
846 #[test]
847 fn test_quantum_word_embedding() {
848 let config = QNLPConfig {
849 vocab_size: 100,
850 text_qubits: 4,
851 ..Default::default()
852 };
853
854 let embedding = QuantumWordEmbedding::new(config);
855 assert_eq!(embedding.num_qubits(), 4);
856
857 let word_ids = vec![1, 5, 10];
859 let gates = embedding
860 .encode_sequence(&word_ids)
861 .expect("Failed to encode sequence");
862 assert!(!gates.is_empty());
863 }
864
865 #[test]
866 fn test_quantum_attention() {
867 let attention = QuantumAttention::new(8, 2);
868 assert_eq!(attention.num_qubits(), 8);
869 assert_eq!(attention.num_heads, 2);
870
871 let gates = attention
872 .attention_gates()
873 .expect("Failed to get attention gates");
874 assert!(!gates.is_empty());
875 }
876
877 #[test]
878 fn test_quantum_text_classifier() {
879 let config = QNLPConfig {
880 vocab_size: 50,
881 text_qubits: 4,
882 feature_qubits: 2,
883 ..Default::default()
884 };
885
886 let classifier = QuantumTextClassifier::new(config, 3);
887
888 let word_ids = vec![1, 2, 3];
890 let probs = classifier
891 .classify(&word_ids)
892 .expect("Failed to classify text");
893 assert_eq!(probs.len(), 3);
894
895 let sum: f64 = probs.iter().sum();
897 assert!((sum - 1.0).abs() < 1e-10);
898 }
899
900 #[test]
901 fn test_quantum_language_model() {
902 let config = QNLPConfig {
903 vocab_size: 20,
904 text_qubits: 4,
905 max_sequence_length: 8,
906 ..Default::default()
907 };
908
909 let lm = QuantumLanguageModel::new(config);
910
911 let context = vec![1, 2, 3];
913 let probs = lm
914 .predict_next_token(&context)
915 .expect("Failed to predict next token");
916 assert_eq!(probs.len(), 20);
917
918 let generated = lm
920 .generate_text(&context, 5, 1.0)
921 .expect("Failed to generate text");
922 assert_eq!(generated.len(), 8); }
924
925 #[test]
926 fn test_text_classifier_training() {
927 let config = QNLPConfig {
928 vocab_size: 10,
929 text_qubits: 3,
930 feature_qubits: 2,
931 ..Default::default()
932 };
933
934 let mut classifier = QuantumTextClassifier::new(config, 2);
935
936 let training_data = vec![
938 (vec![1, 2], 0), (vec![3, 4], 1), (vec![1, 3], 0), (vec![2, 4], 1), ];
943
944 let losses = classifier
945 .train(&training_data, 0.01, 5)
946 .expect("Failed to train classifier");
947 assert_eq!(losses.len(), 5);
948 }
949
950 #[test]
951 fn test_classify_returns_real_distribution() {
952 let config = QNLPConfig {
953 vocab_size: 50,
954 text_qubits: 3,
955 feature_qubits: 2,
956 ..Default::default()
957 };
958 let classifier = QuantumTextClassifier::new(config, 3);
959
960 let probs = classifier.classify(&[1, 5, 9]).expect("classify");
961 assert_eq!(probs.len(), 3);
962 let sum: f64 = probs.iter().sum();
963 assert!(
964 (sum - 1.0).abs() < 1e-9,
965 "class probabilities must sum to 1"
966 );
967 assert!(probs.iter().all(|&p| (-1e-12..=1.0 + 1e-9).contains(&p)));
968 let uniform = 1.0 / 3.0;
972 let max_dev = probs
973 .iter()
974 .map(|&p| (p - uniform).abs())
975 .fold(0.0, f64::max);
976 assert!(
977 max_dev > 1e-9,
978 "real circuit should not give exactly uniform output"
979 );
980 }
981
982 #[test]
983 fn test_predict_next_token_returns_real_distribution() {
984 let config = QNLPConfig {
985 vocab_size: 8,
986 text_qubits: 3,
987 ..Default::default()
988 };
989 let lm = QuantumLanguageModel::new(config);
990
991 let probs = lm.predict_next_token(&[1, 2]).expect("predict");
992 assert_eq!(probs.len(), 8);
993 let sum: f64 = probs.iter().sum();
994 assert!(
995 (sum - 1.0).abs() < 1e-9,
996 "token probabilities must sum to 1"
997 );
998 assert!(probs.iter().all(|&p| (-1e-12..=1.0 + 1e-9).contains(&p)));
999 }
1000}
1001
1002pub mod advanced {
1004 use super::*;
1005
1006 pub struct QuantumTextPreprocessor {
1008 vocab: HashMap<String, usize>,
1010 reverse_vocab: HashMap<usize, String>,
1012 special_tokens: HashMap<String, usize>,
1014 }
1015
1016 impl QuantumTextPreprocessor {
1017 pub fn new() -> Self {
1019 let mut special_tokens = HashMap::new();
1020 special_tokens.insert("<PAD>".to_string(), 0);
1021 special_tokens.insert("<UNK>".to_string(), 1);
1022 special_tokens.insert("<START>".to_string(), 2);
1023 special_tokens.insert("<END>".to_string(), 3);
1024
1025 Self {
1026 vocab: HashMap::new(),
1027 reverse_vocab: HashMap::new(),
1028 special_tokens,
1029 }
1030 }
1031
1032 pub fn build_vocab(&mut self, texts: &[String], max_vocab_size: usize) {
1034 let mut word_counts: HashMap<String, usize> = HashMap::new();
1035
1036 for text in texts {
1038 for word in text.split_whitespace() {
1039 *word_counts.entry(word.to_lowercase()).or_insert(0) += 1;
1040 }
1041 }
1042
1043 let mut word_freq: Vec<_> = word_counts.into_iter().collect();
1045 word_freq.sort_by_key(|b| std::cmp::Reverse(b.1));
1046
1047 for (token, id) in &self.special_tokens {
1049 self.vocab.insert(token.clone(), *id);
1050 self.reverse_vocab.insert(*id, token.clone());
1051 }
1052
1053 let mut vocab_id = self.special_tokens.len();
1055 for (word, _count) in word_freq
1056 .into_iter()
1057 .take(max_vocab_size - self.special_tokens.len())
1058 {
1059 self.vocab.insert(word.clone(), vocab_id);
1060 self.reverse_vocab.insert(vocab_id, word);
1061 vocab_id += 1;
1062 }
1063 }
1064
1065 pub fn tokenize(&self, text: &str) -> Vec<usize> {
1067 let mut tokens = vec![self.special_tokens["<START>"]];
1068
1069 for word in text.split_whitespace() {
1070 let word = word.to_lowercase();
1071 let token_id = self
1072 .vocab
1073 .get(&word)
1074 .copied()
1075 .unwrap_or_else(|| self.special_tokens["<UNK>"]);
1076 tokens.push(token_id);
1077 }
1078
1079 tokens.push(self.special_tokens["<END>"]);
1080 tokens
1081 }
1082
1083 pub fn detokenize(&self, token_ids: &[usize]) -> String {
1085 token_ids
1086 .iter()
1087 .filter_map(|&id| self.reverse_vocab.get(&id))
1088 .filter(|&word| !["<PAD>", "<START>", "<END>"].contains(&word.as_str()))
1089 .cloned()
1090 .collect::<Vec<_>>()
1091 .join(" ")
1092 }
1093
1094 pub fn vocab_size(&self) -> usize {
1096 self.vocab.len()
1097 }
1098 }
1099
1100 pub struct QuantumSemanticSimilarity {
1102 embedding_dim: usize,
1104 num_qubits: usize,
1106 similarity_params: Vec<Parameter>,
1108 }
1109
1110 impl QuantumSemanticSimilarity {
1111 pub fn new(embedding_dim: usize, num_qubits: usize) -> Self {
1113 let mut similarity_params = Vec::new();
1114
1115 for i in 0..num_qubits * 2 {
1117 similarity_params.push(Parameter {
1119 name: format!("sim_{i}"),
1120 value: (i as f64 * 0.1).sin() * 0.5,
1121 bounds: None,
1122 });
1123 }
1124
1125 Self {
1126 embedding_dim,
1127 num_qubits,
1128 similarity_params,
1129 }
1130 }
1131
1132 pub fn compute_similarity(
1134 &self,
1135 text1_tokens: &[usize],
1136 text2_tokens: &[usize],
1137 ) -> QuantRS2Result<f64> {
1138 let config = QNLPConfig {
1140 text_qubits: self.num_qubits,
1141 vocab_size: 1000, ..Default::default()
1143 };
1144
1145 let embedding1 = QuantumWordEmbedding::new(config.clone());
1146 let embedding2 = QuantumWordEmbedding::new(config);
1147
1148 let gates1 = embedding1.encode_sequence(text1_tokens)?;
1150 let gates2 = embedding2.encode_sequence(text2_tokens)?;
1151
1152 let similarity = self.quantum_text_overlap(gates1, gates2)?;
1155
1156 Ok(similarity)
1157 }
1158
1159 fn quantum_text_overlap(
1167 &self,
1168 gates1: Vec<Box<dyn GateOp>>,
1169 gates2: Vec<Box<dyn GateOp>>,
1170 ) -> QuantRS2Result<f64> {
1171 let num_qubits = circuit_num_qubits(&gates1, self.num_qubits)
1172 .max(circuit_num_qubits(&gates2, self.num_qubits));
1173 let psi1 = crate::qml::simulator::simulate(num_qubits, &gates1)?;
1174 let psi2 = crate::qml::simulator::simulate(num_qubits, &gates2)?;
1175
1176 let overlap: Complex64 = psi1
1177 .iter()
1178 .zip(psi2.iter())
1179 .map(|(a, b)| a.conj() * b)
1180 .sum();
1181 Ok(overlap.norm_sqr())
1182 }
1183 }
1184
1185 pub struct QuantumTextSummarizer {
1187 config: QNLPConfig,
1189 encoder: QuantumWordEmbedding,
1191 attention: QuantumAttention,
1193 summary_params: Vec<Parameter>,
1195 }
1196
1197 impl QuantumTextSummarizer {
1198 pub fn new(config: QNLPConfig) -> Self {
1200 let encoder = QuantumWordEmbedding::new(config.clone());
1201 let attention = QuantumAttention::new(config.text_qubits, config.num_attention_heads);
1202
1203 let mut summary_params = Vec::new();
1204 for i in 0..config.text_qubits {
1205 summary_params.push(Parameter {
1206 name: format!("summary_{i}"),
1207 value: (i as f64 * 0.15).sin() * 0.4,
1208 bounds: None,
1209 });
1210 }
1211
1212 Self {
1213 config,
1214 encoder,
1215 attention,
1216 summary_params,
1217 }
1218 }
1219
1220 pub fn extractive_summarize(
1222 &self,
1223 text_tokens: &[usize],
1224 summary_length: usize,
1225 ) -> QuantRS2Result<Vec<usize>> {
1226 let _encoding_gates = self.encoder.encode_sequence(text_tokens)?;
1228
1229 let _attention_gates = self.attention.attention_gates()?;
1231
1232 let mut token_scores = Vec::new();
1234 for (i, &token) in text_tokens.iter().enumerate() {
1235 let position_weight = (i as f64 / text_tokens.len() as f64).mul_add(-0.5, 1.0);
1237 let token_weight = (token as f64 * 0.1).sin().abs();
1238 let score = position_weight * token_weight;
1239 token_scores.push((i, token, score));
1240 }
1241
1242 token_scores.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
1244
1245 let mut summary_tokens = Vec::new();
1246 for (_, token, _) in token_scores.into_iter().take(summary_length) {
1247 summary_tokens.push(token);
1248 }
1249
1250 Ok(summary_tokens)
1251 }
1252
1253 pub fn abstractive_summarize(
1255 &self,
1256 _text_tokens: &[usize],
1257 _summary_length: usize,
1258 ) -> QuantRS2Result<Vec<usize>> {
1259 Ok(vec![1, 2, 3]) }
1263 }
1264
1265 pub struct QuantumNamedEntityRecognition {
1267 config: QNLPConfig,
1269 encoder: QuantumWordEmbedding,
1271 entity_classifiers: HashMap<String, Vec<Parameter>>,
1273 entity_types: Vec<String>,
1275 }
1276
1277 impl QuantumNamedEntityRecognition {
1278 pub fn new(config: QNLPConfig) -> Self {
1280 let encoder = QuantumWordEmbedding::new(config.clone());
1281 let entity_types = vec![
1282 "PERSON".to_string(),
1283 "ORGANIZATION".to_string(),
1284 "LOCATION".to_string(),
1285 "DATE".to_string(),
1286 "MONEY".to_string(),
1287 ];
1288
1289 let mut entity_classifiers = HashMap::new();
1290 for entity_type in &entity_types {
1291 let mut classifier_params = Vec::new();
1292 for i in 0..config.text_qubits {
1293 classifier_params.push(Parameter {
1294 name: format!("{entity_type}_{i}"),
1295 value: (i as f64).mul_add(0.1, entity_type.len() as f64).sin() * 0.3,
1296 bounds: None,
1297 });
1298 }
1299 entity_classifiers.insert(entity_type.clone(), classifier_params);
1300 }
1301
1302 Self {
1303 config,
1304 encoder,
1305 entity_classifiers,
1306 entity_types,
1307 }
1308 }
1309
1310 pub fn recognize_entities(
1312 &self,
1313 text_tokens: &[usize],
1314 ) -> QuantRS2Result<Vec<(usize, usize, String)>> {
1315 let mut entities = Vec::new();
1316
1317 for start in 0..text_tokens.len() {
1319 for end in start + 1..=text_tokens.len().min(start + 5) {
1320 let entity_tokens = &text_tokens[start..end];
1322
1323 if let Some(entity_type) = self.classify_span(entity_tokens)? {
1325 entities.push((start, end, entity_type));
1326 }
1327 }
1328 }
1329
1330 entities.sort_by_key(|b| std::cmp::Reverse(b.1 - b.0));
1332 let mut final_entities = Vec::new();
1333 let mut used_positions = vec![false; text_tokens.len()];
1334
1335 for (start, end, entity_type) in entities {
1336 if used_positions[start..end].iter().all(|&used| !used) {
1337 for pos in start..end {
1338 used_positions[pos] = true;
1339 }
1340 final_entities.push((start, end, entity_type));
1341 }
1342 }
1343
1344 final_entities.sort_by_key(|&(start, _, _)| start);
1345 Ok(final_entities)
1346 }
1347
1348 fn classify_span(&self, tokens: &[usize]) -> QuantRS2Result<Option<String>> {
1350 let _encoding_gates = self.encoder.encode_sequence(tokens)?;
1352
1353 let mut best_score = 0.0;
1354 let mut best_type = None;
1355
1356 for entity_type in &self.entity_types {
1358 let score = self.compute_entity_score(tokens, entity_type)?;
1359 if score > best_score && score > 0.5 {
1360 best_score = score;
1362 best_type = Some(entity_type.clone());
1363 }
1364 }
1365
1366 Ok(best_type)
1367 }
1368
1369 fn compute_entity_score(&self, tokens: &[usize], entity_type: &str) -> QuantRS2Result<f64> {
1371 let mut score = 0.0;
1373
1374 for &token in tokens {
1375 match entity_type {
1377 "PERSON" => {
1378 if token % 7 == 1 {
1379 score += 0.3;
1381 }
1382 }
1383 "LOCATION" => {
1384 if token % 5 == 2 {
1385 score += 0.3;
1387 }
1388 }
1389 "ORGANIZATION" => {
1390 if token % 11 == 3 {
1391 score += 0.3;
1393 }
1394 }
1395 "DATE" => {
1396 if token % 13 == 4 {
1397 score += 0.3;
1399 }
1400 }
1401 "MONEY" => {
1402 if token % 17 == 5 {
1403 score += 0.3;
1405 }
1406 }
1407 _ => {}
1408 }
1409 }
1410
1411 score /= tokens.len() as f64; Ok(score)
1413 }
1414 }
1415
1416 #[cfg(test)]
1417 mod advanced_tests {
1418 use super::*;
1419
1420 #[test]
1421 fn test_text_overlap_is_real_fidelity_not_constant() {
1422 let sim = QuantumSemanticSimilarity::new(4, 3);
1423
1424 let same = sim
1426 .compute_similarity(&[1, 2, 3], &[1, 2, 3])
1427 .expect("same similarity");
1428 assert!(
1429 (same - 1.0).abs() < 1e-9,
1430 "identical texts must have fidelity 1.0, got {same} (old fabrication returned 0.7)"
1431 );
1432
1433 let diff = sim
1435 .compute_similarity(&[1, 2, 3], &[3, 2, 1])
1436 .expect("diff similarity");
1437 assert!((0.0..=1.0 + 1e-9).contains(&diff));
1438 assert!(
1439 (diff - same).abs() > 1e-9 || (diff - 0.7).abs() > 1e-9,
1440 "similarity must be a real computed fidelity, not a constant"
1441 );
1442 }
1443 }
1444}
1445
1446pub use advanced::*;