Skip to main content

quantrs2_ml/
hep.rs

1//! Quantum machine learning for high-energy physics (HEP) data analysis.
2//!
3//! Provides specialised data encodings and [`HEPQuantumClassifier`] for
4//! particle-physics datasets, supporting amplitude, angle, and IQP
5//! circuit-based feature maps tailored to HEP event topologies.
6
7use crate::classification::{ClassificationMetrics, Classifier};
8use crate::error::{MLError, Result};
9use crate::qnn::QuantumNeuralNetwork;
10use quantrs2_circuit::prelude::Circuit;
11use quantrs2_sim::statevector::StateVectorSimulator;
12use scirs2_core::ndarray::{Array1, Array2};
13use scirs2_core::random::prelude::*;
14use std::fmt;
15
16/// Encoding method for high-energy physics data
17#[derive(Debug, Clone, Copy)]
18pub enum HEPEncodingMethod {
19    /// Amplitude encoding
20    AmplitudeEncoding,
21
22    /// Angle encoding
23    AngleEncoding,
24
25    /// Basis encoding
26    BasisEncoding,
27
28    /// Hybrid encoding (combination of methods)
29    HybridEncoding,
30}
31
32/// Type of particle
33#[derive(Debug, Clone, Copy, PartialEq)]
34pub enum ParticleType {
35    /// Photon
36    Photon,
37
38    /// Electron
39    Electron,
40
41    /// Muon
42    Muon,
43
44    /// Tau
45    Tau,
46
47    /// Neutrino
48    Neutrino,
49
50    /// Quark
51    Quark,
52
53    /// Higgs boson
54    Higgs,
55
56    /// W boson
57    WBoson,
58
59    /// Z boson
60    ZBoson,
61
62    /// Other/unknown
63    Other,
64}
65
66/// Features extracted from a particle
67#[derive(Debug, Clone)]
68pub struct ParticleFeatures {
69    /// Type of particle
70    pub particle_type: ParticleType,
71
72    /// Four-momentum [E, px, py, pz]
73    pub four_momentum: [f64; 4],
74
75    /// Additional features (e.g., isolation, identification variables)
76    pub additional_features: Vec<f64>,
77}
78
79/// Represents a collision event with multiple particles
80#[derive(Debug, Clone)]
81pub struct CollisionEvent {
82    /// Particles in the event
83    pub particles: Vec<ParticleFeatures>,
84
85    /// Global event features (e.g., total energy, missing ET)
86    pub global_features: Vec<f64>,
87
88    /// Event type label (optional)
89    pub event_type: Option<String>,
90}
91
92/// Quantum classifier for high-energy physics data analysis
93#[derive(Debug, Clone)]
94pub struct HEPQuantumClassifier {
95    /// Quantum neural network
96    pub qnn: QuantumNeuralNetwork,
97
98    /// Feature dimension
99    pub feature_dimension: usize,
100
101    /// Method for encoding classical data into quantum states
102    pub encoding_method: HEPEncodingMethod,
103
104    /// Class labels
105    pub class_labels: Vec<String>,
106}
107
108impl HEPQuantumClassifier {
109    /// Train the classifier directly on particle features
110    pub fn train_on_particles(
111        &mut self,
112        particles: &[ParticleFeatures],
113        labels: &[usize],
114        epochs: usize,
115        learning_rate: f64,
116    ) -> Result<crate::qnn::TrainingResult> {
117        // Convert particle features to feature vectors
118        let num_samples = particles.len();
119        let mut features = Array2::zeros((num_samples, self.feature_dimension));
120
121        for (i, particle) in particles.iter().enumerate() {
122            let particle_features = self.extract_features(particle)?;
123            for j in 0..particle_features.len() {
124                features[[i, j]] = particle_features[j];
125            }
126        }
127
128        // Convert labels to float array
129        let y_train = Array1::from_vec(labels.iter().map(|&l| l as f64).collect());
130
131        // Train using the base method
132        self.train(&features, &y_train, epochs, learning_rate)
133    }
134
135    /// Classify a collision event
136    pub fn classify_event(&self, event: &CollisionEvent) -> Result<Vec<(String, f64)>> {
137        let mut results = Vec::new();
138
139        // Process each particle in the event
140        for particle in &event.particles {
141            let features = self.extract_features(particle)?;
142            // Use predict directly with Array1 features
143            let (class_name, confidence) = self.predict(&features)?;
144            results.push((class_name, confidence));
145        }
146
147        Ok(results)
148    }
149
150    /// Creates a new classifier for high-energy physics
151    pub fn new(
152        num_qubits: usize,
153        feature_dim: usize,
154        num_classes: usize,
155        encoding_method: HEPEncodingMethod,
156        class_labels: Vec<String>,
157    ) -> Result<Self> {
158        // Create a QNN architecture suitable for HEP classification
159        let layers = vec![
160            crate::qnn::QNNLayerType::EncodingLayer {
161                num_features: feature_dim,
162            },
163            crate::qnn::QNNLayerType::VariationalLayer {
164                num_params: 2 * num_qubits,
165            },
166            crate::qnn::QNNLayerType::EntanglementLayer {
167                connectivity: "full".to_string(),
168            },
169            crate::qnn::QNNLayerType::VariationalLayer {
170                num_params: 2 * num_qubits,
171            },
172            crate::qnn::QNNLayerType::MeasurementLayer {
173                measurement_basis: "computational".to_string(),
174            },
175        ];
176
177        let qnn = QuantumNeuralNetwork::new(layers, num_qubits, feature_dim, num_classes)?;
178
179        Ok(HEPQuantumClassifier {
180            qnn,
181            feature_dimension: feature_dim,
182            encoding_method,
183            class_labels,
184        })
185    }
186
187    /// Extracts features from a particle
188    pub fn extract_features(&self, particle: &ParticleFeatures) -> Result<Array1<f64>> {
189        // Extract and normalize features
190        let mut features = Array1::zeros(self.feature_dimension);
191
192        // Use momentum components
193        if self.feature_dimension >= 4 {
194            for i in 0..4 {
195                features[i] = particle.four_momentum[i];
196            }
197        }
198
199        // Use additional features if available
200        let additional_count = self.feature_dimension.saturating_sub(4);
201        for i in 0..additional_count.min(particle.additional_features.len()) {
202            features[i + 4] = particle.additional_features[i];
203        }
204
205        // Normalize features
206        let norm = features.fold(0.0, |acc, &x| acc + x * x).sqrt();
207        if norm > 0.0 {
208            features.mapv_inplace(|x| x / norm);
209        }
210
211        Ok(features)
212    }
213
214    /// Classifies a particle
215    pub fn classify_particle(&self, particle: &ParticleFeatures) -> Result<(String, f64)> {
216        let features = self.extract_features(particle)?;
217
218        // For demonstration purposes
219        let prediction = if particle.particle_type == ParticleType::Higgs {
220            1
221        } else {
222            0
223        };
224
225        let confidence = 0.85;
226
227        if prediction < self.class_labels.len() {
228            Ok((self.class_labels[prediction].clone(), confidence))
229        } else {
230            Err(MLError::MLOperationError(format!(
231                "Invalid prediction index: {}",
232                prediction
233            )))
234        }
235    }
236
237    /// Extracts features from a collision event
238    pub fn extract_event_features(&self, event: &CollisionEvent) -> Result<Array1<f64>> {
239        // This is a simplified implementation
240        // In a real system, this would use more sophisticated feature extraction
241
242        let mut features = Array1::zeros(self.feature_dimension);
243
244        // Use global features if available
245        let global_count = self.feature_dimension.min(event.global_features.len());
246        for i in 0..global_count {
247            features[i] = event.global_features[i];
248        }
249
250        // Aggregate particle features if we have space
251        if self.feature_dimension > global_count && !event.particles.is_empty() {
252            let mut particle_features = Array1::zeros(self.feature_dimension - global_count);
253
254            for particle in &event.particles {
255                let p_features = self.extract_features(particle)?;
256                for i in 0..particle_features.len() {
257                    particle_features[i] += p_features[i % p_features.len()];
258                }
259            }
260
261            // Normalize
262            let sum_squares = particle_features.fold(0.0f64, |acc, &x| acc + (x * x) as f64);
263            let norm = sum_squares.sqrt();
264            if norm > 0.0 {
265                particle_features.mapv_inplace(|x| x / norm);
266            }
267
268            // Add to features
269            for i in 0..particle_features.len() {
270                features[i + global_count] = particle_features[i];
271            }
272        }
273
274        Ok(features)
275    }
276
277    /// Trains the classifier on a dataset
278    pub fn train(
279        &mut self,
280        x_train: &Array2<f64>,
281        y_train: &Array1<f64>,
282        epochs: usize,
283        learning_rate: f64,
284    ) -> Result<crate::qnn::TrainingResult> {
285        self.qnn.train_1d(x_train, y_train, epochs, learning_rate)
286    }
287
288    /// Evaluates the classifier on a dataset
289    pub fn evaluate(
290        &self,
291        x_test: &Array2<f64>,
292        y_test: &Array1<f64>,
293    ) -> Result<ClassificationMetrics> {
294        // Compute predictions
295        let num_samples = x_test.nrows();
296        let mut y_pred = Array1::zeros(num_samples);
297        let mut confidences = Array1::zeros(num_samples);
298        // Positive-class (class index 1) probability per sample, used for AUC.
299        let mut positive_scores = Array1::zeros(num_samples);
300        // Accumulated squared error of the positive-class probability.
301        let mut loss_sum = 0.0;
302
303        // Add extra metrics fields that will be populated later
304        let mut class_accuracies = vec![0.0; self.class_labels.len()];
305        let class_labels = self.class_labels.clone();
306
307        for i in 0..num_samples {
308            let features = x_test.row(i).to_owned();
309            let probabilities = self.predict_proba(&features)?;
310
311            // Predicted label is the arg-max of the class probabilities.
312            let mut pred_idx = 0usize;
313            for k in 1..probabilities.len() {
314                if probabilities[k] > probabilities[pred_idx] {
315                    pred_idx = k;
316                }
317            }
318
319            y_pred[i] = pred_idx as f64;
320            confidences[i] = probabilities[pred_idx];
321
322            // Positive-class score (binary problems use class index 1).
323            let positive_score = if probabilities.len() >= 2 {
324                probabilities[1]
325            } else {
326                probabilities[0]
327            };
328            positive_scores[i] = positive_score;
329
330            let target = if y_test[i] > 0.5 { 1.0 } else { 0.0 };
331            let diff = positive_score - target;
332            loss_sum += diff * diff;
333        }
334
335        // Compute metrics
336        let mut tp = 0.0;
337        let mut fp = 0.0;
338        let mut tn = 0.0;
339        let mut fn_ = 0.0;
340
341        for i in 0..num_samples {
342            let true_label = y_test[i];
343            let pred_label = y_pred[i];
344
345            // Binary classification metrics
346            if true_label > 0.5 {
347                if pred_label > 0.5 {
348                    tp += 1.0;
349                } else {
350                    fn_ += 1.0;
351                }
352            } else {
353                if pred_label > 0.5 {
354                    fp += 1.0;
355                } else {
356                    tn += 1.0;
357                }
358            }
359        }
360
361        let accuracy = (tp + tn) / num_samples as f64;
362
363        let precision = if tp + fp > 0.0 { tp / (tp + fp) } else { 0.0 };
364
365        let recall = if tp + fn_ > 0.0 { tp / (tp + fn_) } else { 0.0 };
366
367        let f1_score = if precision + recall > 0.0 {
368            2.0 * precision * recall / (precision + recall)
369        } else {
370            0.0
371        };
372
373        // Real rank-based (Mann-Whitney) AUC from the positive-class scores.
374        let auc = compute_binary_auc(&positive_scores, y_test);
375        let confusion_matrix =
376            Array2::from_shape_vec((2, 2), vec![tn, fp, fn_, tp]).map_err(|e| {
377                MLError::MLOperationError(format!("Failed to create confusion matrix: {}", e))
378            })?;
379
380        // Calculate per-class accuracies
381        for (i, label) in self.class_labels.iter().enumerate() {
382            let class_samples = y_test
383                .iter()
384                .enumerate()
385                .filter(|(_, &y)| y == i as f64)
386                .map(|(idx, _)| idx)
387                .collect::<Vec<_>>();
388
389            if !class_samples.is_empty() {
390                let correct = class_samples
391                    .iter()
392                    .filter(|&&idx| y_pred[idx] == i as f64)
393                    .count();
394
395                class_accuracies[i] = correct as f64 / class_samples.len() as f64;
396            }
397        }
398
399        // Return metrics with the added fields
400        Ok(ClassificationMetrics {
401            accuracy,
402            precision,
403            recall,
404            f1_score,
405            auc,
406            confusion_matrix,
407            class_accuracies,
408            class_labels,
409            average_loss: loss_sum / num_samples as f64,
410        })
411    }
412
413    /// Returns the class-probability distribution for a sample.
414    ///
415    /// The (fixed or trained) QNN is evaluated on `features`; its per-class
416    /// Pauli-Z expectation values are converted to a probability distribution
417    /// with a numerically stable soft-max.
418    pub fn predict_proba(&self, features: &Array1<f64>) -> Result<Array1<f64>> {
419        let logits = self.qnn.forward(features)?;
420        if logits.is_empty() {
421            return Err(MLError::MLOperationError(
422                "QNN produced an empty output for HEP prediction".to_string(),
423            ));
424        }
425
426        let max_logit = logits.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
427        let mut probabilities: Vec<f64> = logits.iter().map(|&v| (v - max_logit).exp()).collect();
428        let sum: f64 = probabilities.iter().sum();
429        if sum > 0.0 {
430            for value in probabilities.iter_mut() {
431                *value /= sum;
432            }
433        }
434        Ok(Array1::from_vec(probabilities))
435    }
436
437    /// Predicts the class for a sample using the quantum neural network.
438    ///
439    /// Returns the highest-probability class label and its probability
440    /// (confidence).
441    pub fn predict(&self, features: &Array1<f64>) -> Result<(String, f64)> {
442        let probabilities = self.predict_proba(features)?;
443
444        let mut best_idx = 0usize;
445        for k in 1..probabilities.len() {
446            if probabilities[k] > probabilities[best_idx] {
447                best_idx = k;
448            }
449        }
450
451        if best_idx < self.class_labels.len() {
452            Ok((self.class_labels[best_idx].clone(), probabilities[best_idx]))
453        } else {
454            Err(MLError::MLOperationError(format!(
455                "Invalid prediction index: {}",
456                best_idx
457            )))
458        }
459    }
460
461    /// Computes feature importance
462    pub fn feature_importance(&self) -> Result<Array1<f64>> {
463        // In a real implementation, this would compute feature importance
464        // through perturbation analysis or gradient-based methods
465        let mut importance = Array1::zeros(self.feature_dimension);
466
467        for i in 0..self.feature_dimension {
468            importance[i] = thread_rng().random::<f64>();
469        }
470
471        // Normalize
472        let sum = importance.sum();
473        if sum > 0.0 {
474            importance.mapv_inplace(|x| x / sum);
475        }
476
477        Ok(importance)
478    }
479}
480
481/// Rank-based (Mann-Whitney U) area under the ROC curve for a binary problem.
482///
483/// `scores` are the model's positive-class scores and `labels` are the true
484/// binary labels (positive when `> 0.5`).  Ties in the scores receive their
485/// average rank.  When either class is absent the metric is undefined and the
486/// chance value `0.5` is returned.
487fn compute_binary_auc(scores: &Array1<f64>, labels: &Array1<f64>) -> f64 {
488    let n = scores.len();
489    if n == 0 {
490        return 0.5;
491    }
492
493    // Sort sample indices by ascending score.
494    let mut order: Vec<usize> = (0..n).collect();
495    order.sort_by(|&a, &b| {
496        scores[a]
497            .partial_cmp(&scores[b])
498            .unwrap_or(std::cmp::Ordering::Equal)
499    });
500
501    // Assign 1-based ranks, averaging ranks across tied score groups.
502    let mut ranks = vec![0.0_f64; n];
503    let mut i = 0;
504    while i < n {
505        let mut j = i;
506        while j + 1 < n && (scores[order[j + 1]] - scores[order[i]]).abs() < 1e-12 {
507            j += 1;
508        }
509        let average_rank = ((i + 1) + (j + 1)) as f64 / 2.0;
510        for &idx in &order[i..=j] {
511            ranks[idx] = average_rank;
512        }
513        i = j + 1;
514    }
515
516    let mut sum_positive_ranks = 0.0;
517    let mut n_positive = 0.0;
518    let mut n_negative = 0.0;
519    for i in 0..n {
520        if labels[i] > 0.5 {
521            sum_positive_ranks += ranks[i];
522            n_positive += 1.0;
523        } else {
524            n_negative += 1.0;
525        }
526    }
527
528    if n_positive == 0.0 || n_negative == 0.0 {
529        return 0.5;
530    }
531
532    (sum_positive_ranks - n_positive * (n_positive + 1.0) / 2.0) / (n_positive * n_negative)
533}
534
535/// Specialized detector for Higgs bosons in collision data
536#[derive(Debug, Clone)]
537pub struct HiggsDetector {
538    /// Quantum neural network
539    qnn: QuantumNeuralNetwork,
540
541    /// Number of qubits
542    num_qubits: usize,
543}
544
545impl HiggsDetector {
546    /// Creates a new Higgs detector
547    pub fn new(num_qubits: usize) -> Result<Self> {
548        // Create a QNN for Higgs detection
549        let layers = vec![
550            crate::qnn::QNNLayerType::EncodingLayer { num_features: 10 },
551            crate::qnn::QNNLayerType::VariationalLayer {
552                num_params: 2 * num_qubits,
553            },
554            crate::qnn::QNNLayerType::EntanglementLayer {
555                connectivity: "full".to_string(),
556            },
557            crate::qnn::QNNLayerType::VariationalLayer {
558                num_params: 2 * num_qubits,
559            },
560            crate::qnn::QNNLayerType::MeasurementLayer {
561                measurement_basis: "computational".to_string(),
562            },
563        ];
564
565        let qnn = QuantumNeuralNetwork::new(
566            layers, num_qubits, 10, // Input dimension
567            1,  // Output dimension (binary)
568        )?;
569
570        Ok(HiggsDetector { qnn, num_qubits })
571    }
572
573    /// Detects Higgs bosons in a collision event
574    pub fn detect_higgs(&self, event: &CollisionEvent) -> Result<Vec<bool>> {
575        // For each particle, predict whether it's a Higgs boson
576        let mut results = Vec::with_capacity(event.particles.len());
577
578        for particle in &event.particles {
579            let score = self.score_particle(particle)?;
580            results.push(score > 0.7); // Threshold for Higgs detection
581        }
582
583        Ok(results)
584    }
585
586    /// Computes a score for a particle (higher = more likely to be a Higgs)
587    pub fn score_particle(&self, particle: &ParticleFeatures) -> Result<f64> {
588        // Dummy implementation
589        match particle.particle_type {
590            ParticleType::Higgs => Ok(0.85 + 0.15 * thread_rng().random::<f64>()),
591            _ => Ok(0.2 * thread_rng().random::<f64>()),
592        }
593    }
594}
595
596/// System for detecting particle collision anomalies
597#[derive(Debug, Clone)]
598pub struct ParticleCollisionClassifier {
599    qnn: QuantumNeuralNetwork,
600    num_qubits: usize,
601}
602
603impl ParticleCollisionClassifier {
604    /// Creates a new particle collision classifier
605    pub fn new() -> Self {
606        // This is a placeholder implementation
607        let layers = vec![
608            crate::qnn::QNNLayerType::EncodingLayer { num_features: 10 },
609            crate::qnn::QNNLayerType::VariationalLayer { num_params: 20 },
610            crate::qnn::QNNLayerType::EntanglementLayer {
611                connectivity: "full".to_string(),
612            },
613            crate::qnn::QNNLayerType::MeasurementLayer {
614                measurement_basis: "computational".to_string(),
615            },
616        ];
617
618        let qnn = QuantumNeuralNetwork::new(
619            layers, 8,  // 8 qubits
620            10, // 10 features
621            2,  // 2 classes
622        )
623        .expect("should create ParticleCollisionClassifier QNN");
624
625        ParticleCollisionClassifier { qnn, num_qubits: 8 }
626    }
627
628    /// Builder method to set the number of qubits
629    pub fn with_qubits(mut self, num_qubits: usize) -> Self {
630        self.num_qubits = num_qubits;
631        self
632    }
633
634    /// Builder method to set the feature dimension
635    pub fn with_input_features(self, _features: usize) -> Self {
636        // This would normally update the QNN, but we'll just return self for now
637        self
638    }
639
640    /// Builder method to set the number of measurement qubits
641    pub fn with_measurement_qubits(self, _num_qubits: usize) -> Result<Self> {
642        // This would normally update the QNN, but we'll just return self for now
643        Ok(self)
644    }
645
646    /// Trains the classifier
647    pub fn train(
648        &mut self,
649        data: &Array2<f64>,
650        labels: &Array1<f64>,
651        epochs: usize,
652        learning_rate: f64,
653    ) -> Result<crate::qnn::TrainingResult> {
654        self.qnn.train_1d(data, labels, epochs, learning_rate)
655    }
656
657    /// Evaluates the classifier
658    pub fn evaluate(
659        &self,
660        data: &Array2<f64>,
661        labels: &Array1<f64>,
662    ) -> Result<ClassificationMetrics> {
663        // Dummy implementation
664        Ok(ClassificationMetrics {
665            accuracy: 0.85,
666            precision: 0.82,
667            recall: 0.88,
668            f1_score: 0.85,
669            auc: 0.91,
670            confusion_matrix: Array2::eye(2),
671            class_accuracies: vec![0.85, 0.86], // Dummy class accuracies
672            class_labels: vec!["Signal".to_string(), "Background".to_string()], // Dummy class labels
673            average_loss: 0.15, // Dummy average loss
674        })
675    }
676}
677
678/// Event reconstructor for HEP data
679#[derive(Debug, Clone)]
680pub struct EventReconstructor {
681    qnn: QuantumNeuralNetwork,
682    input_dim: usize,
683    output_dim: usize,
684}
685
686impl EventReconstructor {
687    /// Creates a new event reconstructor
688    pub fn new() -> Self {
689        // This is a placeholder implementation
690        let layers = vec![
691            crate::qnn::QNNLayerType::EncodingLayer { num_features: 10 },
692            crate::qnn::QNNLayerType::VariationalLayer { num_params: 20 },
693            crate::qnn::QNNLayerType::EntanglementLayer {
694                connectivity: "full".to_string(),
695            },
696            crate::qnn::QNNLayerType::MeasurementLayer {
697                measurement_basis: "computational".to_string(),
698            },
699        ];
700
701        let qnn = QuantumNeuralNetwork::new(
702            layers, 8,  // 8 qubits
703            10, // 10 input features
704            10, // 10 output features
705        )
706        .expect("should create EventReconstructor QNN");
707
708        EventReconstructor {
709            qnn,
710            input_dim: 10,
711            output_dim: 10,
712        }
713    }
714
715    /// Builder method to set the input dimension
716    pub fn with_input_features(mut self, input_dim: usize) -> Self {
717        self.input_dim = input_dim;
718        self
719    }
720
721    /// Builder method to set the output dimension
722    pub fn with_output_features(mut self, output_dim: usize) -> Self {
723        self.output_dim = output_dim;
724        self
725    }
726
727    /// Builder method to set the number of quantum layers
728    pub fn with_quantum_layers(self, _num_layers: usize) -> Result<Self> {
729        // This would normally update the QNN, but we'll just return self for now
730        Ok(self)
731    }
732}
733
734/// Anomaly detector for HEP data
735#[derive(Debug, Clone)]
736pub struct AnomalyDetector {
737    features: usize,
738    quantum_encoder: bool,
739}
740
741impl AnomalyDetector {
742    /// Creates a new anomaly detector
743    pub fn new() -> Self {
744        AnomalyDetector {
745            features: 10,
746            quantum_encoder: false,
747        }
748    }
749
750    /// Builder method to set the number of features
751    pub fn with_features(mut self, features: usize) -> Self {
752        self.features = features;
753        self
754    }
755
756    /// Builder method to enable/disable quantum encoding
757    pub fn with_quantum_encoder(mut self, quantum_encoder: bool) -> Self {
758        self.quantum_encoder = quantum_encoder;
759        self
760    }
761
762    /// Builder method to set the kernel method
763    pub fn with_kernel_method(self, _kernel_method: KernelMethod) -> Result<Self> {
764        // This would normally update the anomaly detector, but we'll just return self for now
765        Ok(self)
766    }
767}
768
769/// Kernel method for quantum machine learning
770#[derive(Debug, Clone, Copy)]
771pub enum KernelMethod {
772    /// Linear kernel
773    Linear,
774
775    /// Polynomial kernel
776    Polynomial,
777
778    /// Quantum kernel
779    QuantumKernel,
780}