#![allow(
clippy::pedantic,
clippy::unnecessary_wraps,
clippy::needless_range_loop,
clippy::useless_vec,
clippy::needless_collect,
clippy::too_many_arguments
)]
use quantrs2_ml::prelude::*;
use quantrs2_ml::qnn::QNNLayerType;
use scirs2_core::ndarray::{Array1, Array2};
use std::collections::HashMap;
fn main() -> Result<()> {
println!("=== Quantum Explainable AI Demo ===\n");
println!("1. Quantum Feature Attribution...");
feature_attribution_demo()?;
println!("\n2. Circuit Visualization and Analysis...");
circuit_analysis_demo()?;
println!("\n3. Quantum State Analysis...");
quantum_state_demo()?;
println!("\n4. Quantum Saliency Mapping...");
saliency_mapping_demo()?;
println!("\n5. Quantum LIME (Local Interpretable Model-agnostic Explanations)...");
quantum_lime_demo()?;
println!("\n6. Quantum SHAP (SHapley Additive exPlanations)...");
quantum_shap_demo()?;
println!("\n7. Layer-wise Relevance Propagation...");
quantum_lrp_demo()?;
println!("\n8. Comprehensive Explanation Analysis...");
comprehensive_explanation_demo()?;
println!("\n=== Quantum Explainable AI Demo Complete ===");
Ok(())
}
fn feature_attribution_demo() -> Result<()> {
let layers = vec![
QNNLayerType::EncodingLayer { num_features: 4 },
QNNLayerType::VariationalLayer { num_params: 12 },
QNNLayerType::EntanglementLayer {
connectivity: "circular".to_string(),
},
QNNLayerType::VariationalLayer { num_params: 8 },
QNNLayerType::MeasurementLayer {
measurement_basis: "computational".to_string(),
},
];
let model = QuantumNeuralNetwork::new(layers, 4, 4, 2)?;
println!(
" Created quantum model with {} parameters",
model.parameters.len()
);
let attribution_methods = vec![
(
"Integrated Gradients",
ExplanationMethod::QuantumFeatureAttribution {
method: AttributionMethod::IntegratedGradients,
num_samples: 50,
baseline: Some(Array1::zeros(4)),
},
),
(
"Gradient × Input",
ExplanationMethod::QuantumFeatureAttribution {
method: AttributionMethod::GradientInput,
num_samples: 1,
baseline: None,
},
),
(
"Gradient SHAP",
ExplanationMethod::QuantumFeatureAttribution {
method: AttributionMethod::GradientSHAP,
num_samples: 30,
baseline: None,
},
),
(
"Quantum Attribution",
ExplanationMethod::QuantumFeatureAttribution {
method: AttributionMethod::QuantumAttribution,
num_samples: 25,
baseline: None,
},
),
];
let test_input = Array1::from_vec(vec![0.8, 0.3, 0.9, 0.1]);
println!(
"\n Feature attribution analysis for input: [{:.1}, {:.1}, {:.1}, {:.1}]",
test_input[0], test_input[1], test_input[2], test_input[3]
);
for (method_name, method) in attribution_methods {
let mut xai = QuantumExplainableAI::new(model.clone(), vec![method]);
let background_data = Array2::from_shape_fn((20, 4), |(_, j)| {
0.3f64.mul_add((j as f64 * 0.2).sin(), 0.5)
});
xai.set_background_data(background_data);
let explanation = xai.explain(&test_input)?;
if let Some(ref attributions) = explanation.feature_attributions {
println!("\n {method_name} Attribution:");
for (i, &attr) in attributions.iter().enumerate() {
println!(
" Feature {}: {:+.4} {}",
i,
attr,
if attr.abs() > 0.1 {
if attr > 0.0 {
"(strong positive)"
} else {
"(strong negative)"
}
} else {
"(weak influence)"
}
);
}
let max_idx = attributions
.iter()
.enumerate()
.max_by(|a, b| a.1.abs().partial_cmp(&b.1.abs()).unwrap())
.map_or(0, |(i, _)| i);
println!(
" → Most important feature: Feature {} ({:.4})",
max_idx, attributions[max_idx]
);
}
}
Ok(())
}
fn circuit_analysis_demo() -> Result<()> {
let layers = vec![
QNNLayerType::EncodingLayer { num_features: 4 },
QNNLayerType::VariationalLayer { num_params: 6 },
QNNLayerType::EntanglementLayer {
connectivity: "full".to_string(),
},
QNNLayerType::VariationalLayer { num_params: 6 },
QNNLayerType::MeasurementLayer {
measurement_basis: "Pauli-Z".to_string(),
},
];
let model = QuantumNeuralNetwork::new(layers, 4, 4, 2)?;
let method = ExplanationMethod::CircuitVisualization {
include_measurements: true,
parameter_sensitivity: true,
};
let mut xai = QuantumExplainableAI::new(model, vec![method]);
println!(" Analyzing quantum circuit structure and parameter importance...");
let test_input = Array1::from_vec(vec![0.6, 0.4, 0.7, 0.3]);
let explanation = xai.explain(&test_input)?;
if let Some(ref circuit) = explanation.circuit_explanation {
println!("\n Circuit Analysis Results:");
println!(" Parameter Importance Scores:");
for (i, &importance) in circuit.parameter_importance.iter().enumerate() {
if importance > 0.5 {
println!(" Parameter {i}: {importance:.3} (high importance)");
} else if importance > 0.2 {
println!(" Parameter {i}: {importance:.3} (medium importance)");
}
}
println!("\n Layer-wise Analysis:");
for (i, layer_analysis) in circuit.layer_analysis.iter().enumerate() {
println!(
" Layer {}: {}",
i,
format_layer_type(&layer_analysis.layer_type)
);
println!(
" Information gain: {:.3}",
layer_analysis.information_gain
);
println!(
" Entanglement generated: {:.3}",
layer_analysis.entanglement_generated
);
if layer_analysis.entanglement_generated > 0.5 {
println!(" → Significant entanglement layer");
}
}
println!("\n Gate Contribution Analysis:");
for (i, gate) in circuit.gate_contributions.iter().enumerate().take(5) {
println!(
" Gate {}: {} on qubits {:?}",
gate.gate_index, gate.gate_type, gate.qubits
);
println!(" Contribution: {:.3}", gate.contribution);
if let Some(ref params) = gate.parameters {
println!(" Parameters: {:.3}", params[0]);
}
}
println!("\n Critical Path (most important parameters):");
print!(" ");
for (i, ¶m_idx) in circuit.critical_path.iter().enumerate() {
if i > 0 {
print!(" → ");
}
print!("P{param_idx}");
}
println!();
println!(" → This path represents the most influential quantum operations");
}
Ok(())
}
fn quantum_state_demo() -> Result<()> {
let layers = vec![
QNNLayerType::EncodingLayer { num_features: 3 },
QNNLayerType::VariationalLayer { num_params: 9 },
QNNLayerType::EntanglementLayer {
connectivity: "circular".to_string(),
},
QNNLayerType::MeasurementLayer {
measurement_basis: "computational".to_string(),
},
];
let model = QuantumNeuralNetwork::new(layers, 3, 3, 2)?;
let method = ExplanationMethod::StateAnalysis {
entanglement_measures: true,
coherence_analysis: true,
superposition_analysis: true,
};
let mut xai = QuantumExplainableAI::new(model, vec![method]);
println!(" Analyzing quantum state properties...");
let test_inputs = [
Array1::from_vec(vec![0.0, 0.0, 0.0]),
Array1::from_vec(vec![1.0, 0.0, 0.0]),
Array1::from_vec(vec![0.5, 0.5, 0.5]),
Array1::from_vec(vec![1.0, 1.0, 1.0]),
];
for (i, input) in test_inputs.iter().enumerate() {
println!(
"\n Input {}: [{:.1}, {:.1}, {:.1}]",
i + 1,
input[0],
input[1],
input[2]
);
let explanation = xai.explain(input)?;
if let Some(ref state) = explanation.state_properties {
println!(" Quantum State Properties:");
println!(
" - Entanglement entropy: {:.3}",
state.entanglement_entropy
);
for (measure_name, &value) in &state.coherence_measures {
println!(" - {measure_name}: {value:.3}");
}
let max_component = state
.superposition_components
.iter()
.copied()
.fold(f64::NEG_INFINITY, f64::max);
println!(" - Max superposition component: {max_component:.3}");
let total_prob = state.measurement_probabilities.sum();
println!(" - Total measurement probability: {total_prob:.3}");
let most_likely = state
.measurement_probabilities
.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
.map_or((0, 0.0), |(idx, &prob)| (idx, prob));
println!(
" - Most likely outcome: state {} with prob {:.3}",
most_likely.0, most_likely.1
);
if let Some(highest_fidelity) = state
.state_fidelities
.values()
.copied()
.fold(None, |acc, x| Some(acc.map_or(x, |y| f64::max(x, y))))
{
println!(" - Highest basis state fidelity: {highest_fidelity:.3}");
}
if state.entanglement_entropy > 0.5 {
println!(" → Highly entangled state");
} else if state.entanglement_entropy > 0.1 {
println!(" → Moderately entangled state");
} else {
println!(" → Separable or weakly entangled state");
}
}
}
Ok(())
}
fn saliency_mapping_demo() -> Result<()> {
let layers = vec![
QNNLayerType::EncodingLayer { num_features: 4 },
QNNLayerType::VariationalLayer { num_params: 8 },
QNNLayerType::MeasurementLayer {
measurement_basis: "computational".to_string(),
},
];
let model = QuantumNeuralNetwork::new(layers, 4, 4, 2)?;
let perturbation_methods = vec![
(
"Gaussian Noise",
PerturbationMethod::Gaussian { sigma: 0.1 },
),
(
"Quantum Phase",
PerturbationMethod::QuantumPhase { magnitude: 0.2 },
),
("Feature Masking", PerturbationMethod::FeatureMasking),
(
"Parameter Perturbation",
PerturbationMethod::ParameterPerturbation { strength: 0.1 },
),
];
let test_input = Array1::from_vec(vec![0.7, 0.2, 0.8, 0.4]);
println!(" Computing saliency maps with different perturbation methods...");
println!(
" Input: [{:.1}, {:.1}, {:.1}, {:.1}]",
test_input[0], test_input[1], test_input[2], test_input[3]
);
for (method_name, perturbation_method) in perturbation_methods {
let method = ExplanationMethod::SaliencyMapping {
perturbation_method,
aggregation: AggregationMethod::Mean,
};
let mut xai = QuantumExplainableAI::new(model.clone(), vec![method]);
let explanation = xai.explain(&test_input)?;
if let Some(ref saliency) = explanation.saliency_map {
println!("\n {method_name} Saliency Map:");
for output_idx in 0..saliency.ncols() {
println!(" Output {output_idx}:");
for input_idx in 0..saliency.nrows() {
let saliency_score = saliency[[input_idx, output_idx]];
if saliency_score > 0.1 {
println!(
" Feature {input_idx} → Output {output_idx}: {saliency_score:.3} (important)"
);
} else if saliency_score > 0.05 {
println!(
" Feature {input_idx} → Output {output_idx}: {saliency_score:.3} (moderate)"
);
}
}
}
let mut max_saliency = 0.0;
let mut max_pair = (0, 0);
for i in 0..saliency.nrows() {
for j in 0..saliency.ncols() {
if saliency[[i, j]] > max_saliency {
max_saliency = saliency[[i, j]];
max_pair = (i, j);
}
}
}
println!(
" → Most salient: Feature {} → Output {} ({:.3})",
max_pair.0, max_pair.1, max_saliency
);
}
}
Ok(())
}
fn quantum_lime_demo() -> Result<()> {
let layers = vec![
QNNLayerType::EncodingLayer { num_features: 4 },
QNNLayerType::VariationalLayer { num_params: 10 },
QNNLayerType::EntanglementLayer {
connectivity: "circular".to_string(),
},
QNNLayerType::MeasurementLayer {
measurement_basis: "computational".to_string(),
},
];
let model = QuantumNeuralNetwork::new(layers, 4, 4, 2)?;
let local_models = vec![
("Linear Regression", LocalModelType::LinearRegression),
("Decision Tree", LocalModelType::DecisionTree),
("Quantum Linear", LocalModelType::QuantumLinear),
];
let test_input = Array1::from_vec(vec![0.6, 0.8, 0.2, 0.9]);
println!(" Quantum LIME: Local Interpretable Model-agnostic Explanations");
println!(
" Input: [{:.1}, {:.1}, {:.1}, {:.1}]",
test_input[0], test_input[1], test_input[2], test_input[3]
);
for (model_name, local_model) in local_models {
let method = ExplanationMethod::QuantumLIME {
num_perturbations: 100,
kernel_width: 0.5,
local_model,
};
let mut xai = QuantumExplainableAI::new(model.clone(), vec![method]);
let explanation = xai.explain(&test_input)?;
if let Some(ref attributions) = explanation.feature_attributions {
println!("\n LIME with {model_name}:");
for (i, &attr) in attributions.iter().enumerate() {
let impact = if attr.abs() > 0.3 {
"high"
} else if attr.abs() > 0.1 {
"medium"
} else {
"low"
};
println!(" Feature {i}: {attr:+.3} ({impact} impact)");
}
match model_name {
"Linear Regression" => {
println!(" → Linear relationship approximation in local region");
}
"Decision Tree" => {
println!(" → Rule-based approximation with thresholds");
}
"Quantum Linear" => {
println!(" → Quantum-aware linear approximation");
}
_ => {}
}
let local_complexity = attributions.iter().map(|x| x.abs()).sum::<f64>();
println!(" → Local explanation complexity: {local_complexity:.3}");
}
}
Ok(())
}
fn quantum_shap_demo() -> Result<()> {
let layers = vec![
QNNLayerType::EncodingLayer { num_features: 3 },
QNNLayerType::VariationalLayer { num_params: 6 },
QNNLayerType::MeasurementLayer {
measurement_basis: "Pauli-Z".to_string(),
},
];
let model = QuantumNeuralNetwork::new(layers, 3, 3, 2)?;
let method = ExplanationMethod::QuantumSHAP {
num_coalitions: 100,
background_samples: 20,
};
let mut xai = QuantumExplainableAI::new(model, vec![method]);
let background_data = Array2::from_shape_fn((50, 3), |(i, j)| {
0.3f64.mul_add(((i + j) as f64 * 0.1).sin(), 0.5)
});
xai.set_background_data(background_data);
println!(" Quantum SHAP: SHapley Additive exPlanations");
let test_inputs = [
Array1::from_vec(vec![0.1, 0.5, 0.9]),
Array1::from_vec(vec![0.8, 0.3, 0.6]),
Array1::from_vec(vec![0.4, 0.7, 0.2]),
];
for (i, input) in test_inputs.iter().enumerate() {
println!(
"\n Input {}: [{:.1}, {:.1}, {:.1}]",
i + 1,
input[0],
input[1],
input[2]
);
let explanation = xai.explain(input)?;
if let Some(ref shap_values) = explanation.feature_attributions {
println!(" SHAP Values:");
let mut total_shap = 0.0;
for (j, &value) in shap_values.iter().enumerate() {
total_shap += value;
println!(" - Feature {j}: {value:+.4}");
}
println!(" - Sum of SHAP values: {total_shap:.4}");
let mut indexed_shap: Vec<(usize, f64)> = shap_values
.iter()
.enumerate()
.map(|(idx, &val)| (idx, val.abs()))
.collect();
indexed_shap.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
println!(" Feature importance ranking:");
for (rank, (feature_idx, abs_value)) in indexed_shap.iter().enumerate() {
let original_value = shap_values[*feature_idx];
println!(
" {}. Feature {}: {:.4} (|{:.4}|)",
rank + 1,
feature_idx,
original_value,
abs_value
);
}
println!(
" → SHAP values satisfy efficiency property (sum to prediction difference)"
);
println!(" → Each value represents feature's average marginal contribution");
}
}
Ok(())
}
fn quantum_lrp_demo() -> Result<()> {
let layers = vec![
QNNLayerType::EncodingLayer { num_features: 4 },
QNNLayerType::VariationalLayer { num_params: 8 },
QNNLayerType::VariationalLayer { num_params: 6 },
QNNLayerType::MeasurementLayer {
measurement_basis: "computational".to_string(),
},
];
let model = QuantumNeuralNetwork::new(layers, 4, 4, 2)?;
let lrp_rules = vec![
("Epsilon Rule", LRPRule::Epsilon),
("Gamma Rule", LRPRule::Gamma { gamma: 0.25 }),
(
"Alpha-Beta Rule",
LRPRule::AlphaBeta {
alpha: 2.0,
beta: 1.0,
},
),
("Quantum Rule", LRPRule::QuantumRule),
];
let test_input = Array1::from_vec(vec![0.7, 0.1, 0.8, 0.4]);
println!(" Layer-wise Relevance Propagation for Quantum Circuits");
println!(
" Input: [{:.1}, {:.1}, {:.1}, {:.1}]",
test_input[0], test_input[1], test_input[2], test_input[3]
);
for (rule_name, lrp_rule) in lrp_rules {
let method = ExplanationMethod::QuantumLRP {
propagation_rule: lrp_rule,
epsilon: 1e-6,
};
let mut xai = QuantumExplainableAI::new(model.clone(), vec![method]);
let explanation = xai.explain(&test_input)?;
if let Some(ref relevance) = explanation.feature_attributions {
println!("\n LRP with {rule_name}:");
let total_relevance = relevance.sum();
for (i, &rel) in relevance.iter().enumerate() {
let percentage = if total_relevance.abs() > 1e-10 {
rel / total_relevance * 100.0
} else {
0.0
};
println!(" Feature {i}: {rel:.4} ({percentage:.1}% of total relevance)");
}
println!(" Total relevance: {total_relevance:.4}");
match rule_name {
"Epsilon Rule" => {
println!(" → Distributes relevance proportionally to activations");
}
"Gamma Rule" => {
println!(" → Emphasizes positive contributions");
}
"Alpha-Beta Rule" => {
println!(" → Separates positive and negative contributions");
}
"Quantum Rule" => {
println!(" → Accounts for quantum superposition and entanglement");
}
_ => {}
}
}
}
Ok(())
}
fn comprehensive_explanation_demo() -> Result<()> {
let layers = vec![
QNNLayerType::EncodingLayer { num_features: 4 },
QNNLayerType::VariationalLayer { num_params: 12 },
QNNLayerType::EntanglementLayer {
connectivity: "full".to_string(),
},
QNNLayerType::VariationalLayer { num_params: 8 },
QNNLayerType::MeasurementLayer {
measurement_basis: "computational".to_string(),
},
];
let model = QuantumNeuralNetwork::new(layers, 4, 4, 3)?;
let methods = vec![
ExplanationMethod::QuantumFeatureAttribution {
method: AttributionMethod::IntegratedGradients,
num_samples: 30,
baseline: Some(Array1::zeros(4)),
},
ExplanationMethod::CircuitVisualization {
include_measurements: true,
parameter_sensitivity: true,
},
ExplanationMethod::StateAnalysis {
entanglement_measures: true,
coherence_analysis: true,
superposition_analysis: true,
},
ExplanationMethod::ConceptActivation {
concept_datasets: vec!["pattern_A".to_string(), "pattern_B".to_string()],
activation_threshold: 0.3,
},
];
let mut xai = QuantumExplainableAI::new(model, methods);
xai.add_concept(
"pattern_A".to_string(),
Array1::from_vec(vec![1.0, 0.0, 1.0, 0.0]),
);
xai.add_concept(
"pattern_B".to_string(),
Array1::from_vec(vec![0.0, 1.0, 0.0, 1.0]),
);
let background_data = Array2::from_shape_fn((30, 4), |(i, j)| {
0.4f64.mul_add(((i * j) as f64 * 0.15).sin(), 0.3)
});
xai.set_background_data(background_data);
println!(" Comprehensive Quantum Model Explanation");
let test_input = Array1::from_vec(vec![0.9, 0.1, 0.8, 0.2]);
println!(
"\n Analyzing input: [{:.1}, {:.1}, {:.1}, {:.1}]",
test_input[0], test_input[1], test_input[2], test_input[3]
);
let explanation = xai.explain(&test_input)?;
println!("\n === COMPREHENSIVE EXPLANATION RESULTS ===");
if let Some(ref attributions) = explanation.feature_attributions {
println!("\n Feature Attributions:");
for (i, &attr) in attributions.iter().enumerate() {
println!(" - Feature {i}: {attr:+.3}");
}
}
if let Some(ref circuit) = explanation.circuit_explanation {
println!("\n Circuit Analysis Summary:");
let avg_importance = circuit.parameter_importance.mean().unwrap_or(0.0);
println!(" - Average parameter importance: {avg_importance:.3}");
println!(
" - Number of analyzed layers: {}",
circuit.layer_analysis.len()
);
println!(" - Critical path length: {}", circuit.critical_path.len());
}
if let Some(ref state) = explanation.state_properties {
println!("\n Quantum State Properties:");
println!(
" - Entanglement entropy: {:.3}",
state.entanglement_entropy
);
println!(
" - Coherence measures: {} types",
state.coherence_measures.len()
);
let max_measurement_prob = state
.measurement_probabilities
.iter()
.copied()
.fold(f64::NEG_INFINITY, f64::max);
println!(" - Max measurement probability: {max_measurement_prob:.3}");
}
if let Some(ref concepts) = explanation.concept_activations {
println!("\n Concept Activations:");
for (concept, &activation) in concepts {
let similarity = if activation > 0.7 {
"high"
} else if activation > 0.3 {
"medium"
} else {
"low"
};
println!(" - {concept}: {activation:.3} ({similarity} similarity)");
}
}
println!("\n Explanation Confidence Scores:");
for (component, &confidence) in &explanation.confidence_scores {
println!(" - {component}: {confidence:.3}");
}
println!("\n Generated Explanation:");
println!("{}", explanation.textual_explanation);
println!("\n === KEY INSIGHTS ===");
if let Some(ref attributions) = explanation.feature_attributions {
let max_attr_idx = attributions
.iter()
.enumerate()
.max_by(|a, b| a.1.abs().partial_cmp(&b.1.abs()).unwrap())
.map_or(0, |(i, _)| i);
println!(
" • Most influential feature: Feature {} ({:.3})",
max_attr_idx, attributions[max_attr_idx]
);
}
if let Some(ref state) = explanation.state_properties {
if state.entanglement_entropy > 0.5 {
println!(" • Model creates significant quantum entanglement");
}
let coherence_level = state
.coherence_measures
.values()
.copied()
.fold(0.0, f64::max);
if coherence_level > 0.5 {
println!(" • High quantum coherence detected");
}
}
if let Some(ref concepts) = explanation.concept_activations {
if let Some((best_concept, &max_activation)) =
concepts.iter().max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
{
if max_activation > 0.5 {
println!(" • Input strongly matches concept: {best_concept}");
}
}
}
println!(" • Explanation provides multi-faceted interpretation of quantum model behavior");
Ok(())
}
fn format_layer_type(layer_type: &QNNLayerType) -> String {
match layer_type {
QNNLayerType::EncodingLayer { num_features } => {
format!("Encoding Layer ({num_features} features)")
}
QNNLayerType::VariationalLayer { num_params } => {
format!("Variational Layer ({num_params} parameters)")
}
QNNLayerType::EntanglementLayer { connectivity } => {
format!("Entanglement Layer ({connectivity})")
}
QNNLayerType::MeasurementLayer { measurement_basis } => {
format!("Measurement Layer ({measurement_basis})")
}
}
}