use super::tensor::Tensor;
use quantrs2_core::error::{QuantRS2Error, QuantRS2Result};
use scirs2_core::ndarray::{ArrayD, Dimension, IxDyn};
use scirs2_core::Complex64;
use std::collections::{HashMap, HashSet};
pub trait ContractableNetwork {
fn contract_tensors(&mut self, tensor_id1: usize, tensor_id2: usize) -> QuantRS2Result<usize>;
fn optimize_contraction_order(&mut self) -> QuantRS2Result<()>;
}
#[derive(Debug, Clone)]
pub struct ContractionPath {
steps: Vec<(usize, usize)>,
estimated_cost: f64,
}
impl ContractionPath {
pub const fn new(steps: Vec<(usize, usize)>, estimated_cost: f64) -> Self {
Self {
steps,
estimated_cost,
}
}
pub fn steps(&self) -> &[(usize, usize)] {
&self.steps
}
pub const fn estimated_cost(&self) -> f64 {
self.estimated_cost
}
}
pub fn calculate_greedy_contraction_path(
tensors: &HashMap<usize, Tensor>,
connections: &[(super::tensor::TensorIndex, super::tensor::TensorIndex)],
) -> QuantRS2Result<ContractionPath> {
let mut tensor_connections = HashMap::new();
for (t1, t2) in connections {
tensor_connections
.entry(t1.tensor_id)
.or_insert_with(HashSet::new)
.insert(t2.tensor_id);
tensor_connections
.entry(t2.tensor_id)
.or_insert_with(HashSet::new)
.insert(t1.tensor_id);
}
let mut tensor_dims = HashMap::new();
for (&id, tensor) in tensors {
tensor_dims.insert(id, tensor.dimensions.iter().product::<usize>());
}
let mut remaining_tensors: HashSet<usize> = tensors.keys().copied().collect();
let mut steps = Vec::new();
let mut total_cost = 0.0;
while remaining_tensors.len() > 1 {
let mut best_cost = f64::INFINITY;
let mut best_pair = None;
for &t1 in &remaining_tensors {
if let Some(connected) = tensor_connections.get(&t1) {
for &t2 in connected {
if remaining_tensors.contains(&t2) {
let combined_dim = tensor_dims[&t1] * tensor_dims[&t2];
let cost = combined_dim as f64;
if cost < best_cost {
best_cost = cost;
best_pair = Some((t1, t2));
}
}
}
}
}
if let Some((t1, t2)) = best_pair {
steps.push((t1, t2));
total_cost += best_cost;
remaining_tensors.remove(&t1);
remaining_tensors.remove(&t2);
let new_id = t1; remaining_tensors.insert(new_id);
let mut new_connections = HashSet::new();
let mut t1_connected_tensors = Vec::new();
if let Some(t1_connections) = tensor_connections.get(&t1) {
for &connected_tensor in t1_connections {
if connected_tensor != t2 && remaining_tensors.contains(&connected_tensor) {
t1_connected_tensors.push(connected_tensor);
new_connections.insert(connected_tensor);
}
}
}
for connected_tensor in t1_connected_tensors {
if let Some(other_connections) = tensor_connections.get_mut(&connected_tensor) {
other_connections.remove(&t1);
other_connections.remove(&t2);
other_connections.insert(new_id);
}
}
let mut t2_connected_tensors = Vec::new();
if let Some(t2_connections) = tensor_connections.get(&t2) {
for &connected_tensor in t2_connections {
if connected_tensor != t1 && remaining_tensors.contains(&connected_tensor) {
t2_connected_tensors.push(connected_tensor);
new_connections.insert(connected_tensor);
}
}
}
for connected_tensor in t2_connected_tensors {
if let Some(other_connections) = tensor_connections.get_mut(&connected_tensor) {
other_connections.remove(&t1);
other_connections.remove(&t2);
other_connections.insert(new_id);
}
}
tensor_connections.insert(new_id, new_connections);
tensor_dims.insert(new_id, (tensor_dims[&t1] * tensor_dims[&t2]) / 2);
} else {
let mut remaining_vec: Vec<_> = remaining_tensors.iter().copied().collect();
remaining_vec.sort_unstable();
if remaining_vec.len() >= 2 {
let t1 = remaining_vec[0];
let t2 = remaining_vec[1];
steps.push((t1, t2));
total_cost += (tensor_dims[&t1] * tensor_dims[&t2]) as f64;
remaining_tensors.remove(&t1);
remaining_tensors.remove(&t2);
remaining_tensors.insert(t1);
tensor_dims.insert(t1, (tensor_dims[&t1] * tensor_dims[&t2]) / 2);
} else {
break;
}
}
}
Ok(ContractionPath::new(steps, total_cost))
}
pub fn calculate_optimal_contraction_path(
tensors: &HashMap<usize, Tensor>,
connections: &[(super::tensor::TensorIndex, super::tensor::TensorIndex)],
) -> QuantRS2Result<ContractionPath> {
if let Some(path) = identify_circuit_structure(tensors, connections) {
return Ok(path);
}
calculate_greedy_contraction_path(tensors, connections)
}
fn identify_circuit_structure(
tensors: &HashMap<usize, Tensor>,
connections: &[(super::tensor::TensorIndex, super::tensor::TensorIndex)],
) -> Option<ContractionPath> {
let mut tensor_connections = HashMap::new();
for (t1, t2) in connections {
tensor_connections
.entry(t1.tensor_id)
.or_insert_with(HashSet::new)
.insert(t2.tensor_id);
tensor_connections
.entry(t2.tensor_id)
.or_insert_with(HashSet::new)
.insert(t1.tensor_id);
}
let mut tensor_ids: Vec<usize> = tensors.keys().copied().collect();
tensor_ids.sort_unstable();
if is_linear_circuit(&tensor_connections, &tensor_ids) {
let mut steps = Vec::new();
let mut cost = 0.0;
let ordered_tensors = order_linear_circuit(&tensor_connections, &tensor_ids);
for ids in ordered_tensors.windows(2) {
steps.push((ids[0], ids[1]));
cost += 16.0; }
return Some(ContractionPath::new(steps, cost));
}
if is_star_circuit(&tensor_connections, &tensor_ids) {
let mut steps = Vec::new();
let mut cost = 0.0;
let central = find_central_tensor(&tensor_connections);
let leaf_tensors: Vec<_> = tensor_ids
.iter()
.filter(|&&id| {
id != central
&& tensor_connections
.get(&id)
.is_some_and(|conns| conns.contains(¢ral))
})
.copied()
.collect();
for leaf in leaf_tensors {
steps.push((central, leaf));
cost += 16.0; }
return Some(ContractionPath::new(steps, cost));
}
if is_qft_circuit(&tensor_connections, tensors) {
return Some(optimize_qft_circuit(&tensor_connections, tensors));
}
if is_qaoa_circuit(&tensor_connections, tensors) {
return Some(optimize_qaoa_circuit(&tensor_connections, tensors));
}
None
}
fn is_qft_circuit(
tensor_connections: &HashMap<usize, HashSet<usize>>,
tensors: &HashMap<usize, Tensor>,
) -> bool {
let mut hadamard_count = 0;
let mut controlled_phase_count = 0;
let mut swap_count = 0;
for tensor in tensors.values() {
if tensor.rank == 2 {
hadamard_count += 1;
} else if tensor.rank == 4 {
if tensor.dimensions == vec![2, 2, 2, 2] {
controlled_phase_count += 1;
}
if is_swap_like_tensor(tensor) {
swap_count += 1;
}
}
}
hadamard_count > 0 && controlled_phase_count > 0 && hadamard_count >= controlled_phase_count / 2
}
fn is_swap_like_tensor(tensor: &Tensor) -> bool {
tensor.rank == 4 && tensor.dimensions == vec![2, 2, 2, 2]
}
fn optimize_qft_circuit(
tensor_connections: &HashMap<usize, HashSet<usize>>,
tensors: &HashMap<usize, Tensor>,
) -> ContractionPath {
let mut ordered_tensors: Vec<usize> = Vec::new();
let mut tensor_ids: Vec<usize> = tensors.keys().copied().collect();
tensor_ids.sort_unstable();
let mut steps = Vec::new();
let mut cost = 0.0;
let mut layers = identify_qft_layers(tensor_connections, &tensor_ids);
for layer in layers {
for i in 0..layer.len().saturating_sub(1) {
steps.push((layer[i], layer[i + 1]));
cost += 16.0; }
}
if steps.is_empty() {
for i in 0..tensor_ids.len().saturating_sub(1) {
steps.push((tensor_ids[i], tensor_ids[i + 1]));
cost += 16.0;
}
}
ContractionPath::new(steps, cost)
}
fn identify_qft_layers(
tensor_connections: &HashMap<usize, HashSet<usize>>,
tensor_ids: &[usize],
) -> Vec<Vec<usize>> {
let mut degree_groups: HashMap<usize, Vec<usize>> = HashMap::new();
for &id in tensor_ids {
let degree = tensor_connections.get(&id).map_or(0, |conns| conns.len());
degree_groups.entry(degree).or_default().push(id);
}
let mut degrees: Vec<usize> = degree_groups.keys().copied().collect();
degrees.sort_by(|a, b| b.cmp(a));
let mut layers = Vec::new();
for degree in degrees {
if let Some(group) = degree_groups.get(°ree) {
layers.push(group.clone());
}
}
layers
}
fn is_qaoa_circuit(
tensor_connections: &HashMap<usize, HashSet<usize>>,
tensors: &HashMap<usize, Tensor>,
) -> bool {
let mut x_rotation_count = 0;
let mut zz_interaction_count = 0;
for tensor in tensors.values() {
if tensor.rank == 2 {
x_rotation_count += 1; }
else if tensor.rank == 4 {
zz_interaction_count += 1; }
}
x_rotation_count > 0 && zz_interaction_count > 0
}
fn optimize_qaoa_circuit(
tensor_connections: &HashMap<usize, HashSet<usize>>,
tensors: &HashMap<usize, Tensor>,
) -> ContractionPath {
let mut tensor_ids: Vec<usize> = tensors.keys().copied().collect();
tensor_ids.sort_by(|a, b| {
if let (Some(tensor_a), Some(tensor_b)) = (tensors.get(a), tensors.get(b)) {
tensor_b.rank.cmp(&tensor_a.rank) } else {
std::cmp::Ordering::Equal
}
});
let mut rank_groups: HashMap<usize, Vec<usize>> = HashMap::new();
for &id in &tensor_ids {
if let Some(tensor) = tensors.get(&id) {
rank_groups.entry(tensor.rank).or_default().push(id);
}
}
let mut steps = Vec::new();
let mut cost = 0.0;
if let Some(two_qubit_gates) = rank_groups.get(&4) {
for (i, &id1) in two_qubit_gates.iter().enumerate() {
for &id2 in two_qubit_gates.iter().skip(i + 1) {
if tensor_connections
.get(&id1)
.is_some_and(|conns| conns.contains(&id2))
{
steps.push((id1, id2));
cost += 64.0; }
}
}
}
if let Some(single_qubit_gates) = rank_groups.get(&2) {
for (i, &id1) in single_qubit_gates.iter().enumerate() {
for &id2 in single_qubit_gates.iter().skip(i + 1) {
if tensor_connections
.get(&id1)
.is_some_and(|conns| conns.contains(&id2))
{
steps.push((id1, id2));
cost += 16.0; }
}
}
}
if steps.is_empty() {
for i in 0..tensor_ids.len().saturating_sub(1) {
steps.push((tensor_ids[i], tensor_ids[i + 1]));
cost += 16.0; }
}
ContractionPath::new(steps, cost)
}
fn is_linear_circuit(
tensor_connections: &HashMap<usize, HashSet<usize>>,
tensor_ids: &[usize],
) -> bool {
let mut num_endpoints = 0;
for &id in tensor_ids {
let degree = tensor_connections.get(&id).map_or(0, |conns| conns.len());
if degree > 2 {
return false;
} else if degree == 1 {
num_endpoints += 1;
}
}
num_endpoints == 2
}
fn order_linear_circuit(
tensor_connections: &HashMap<usize, HashSet<usize>>,
tensor_ids: &[usize],
) -> Vec<usize> {
let mut result = Vec::new();
let mut current = tensor_ids
.iter()
.find(|&&id| {
tensor_connections
.get(&id)
.is_some_and(|conns| conns.len() == 1)
})
.copied();
if let Some(start) = current {
result.push(start);
let mut visited = HashSet::new();
visited.insert(start);
while let Some(id) = current {
if let Some(connections) = tensor_connections.get(&id) {
let next = connections
.iter()
.find(|&&next_id| !visited.contains(&next_id))
.copied();
if let Some(next_id) = next {
result.push(next_id);
visited.insert(next_id);
current = Some(next_id);
} else {
current = None;
}
} else {
current = None;
}
}
}
if result.len() != tensor_ids.len() {
return tensor_ids.to_vec();
}
result
}
fn is_star_circuit(
tensor_connections: &HashMap<usize, HashSet<usize>>,
tensor_ids: &[usize],
) -> bool {
let mut degree_counts = HashMap::new();
for &id in tensor_ids {
let degree = tensor_connections.get(&id).map_or(0, |conns| conns.len());
*degree_counts.entry(degree).or_insert(0) += 1;
}
let high_degree = degree_counts.keys().filter(|&&d| d > 2).count();
let degree_one = degree_counts.get(&1).copied().unwrap_or(0);
high_degree == 1 && degree_one > 2
}
fn find_central_tensor(tensor_connections: &HashMap<usize, HashSet<usize>>) -> usize {
let mut max_degree = 0;
let mut central = 0;
for (&id, connections) in tensor_connections {
let degree = connections.len();
if degree > max_degree {
max_degree = degree;
central = id;
}
}
central
}
pub(crate) fn shared_axis_pairs(
connections: &[(super::tensor::TensorIndex, super::tensor::TensorIndex)],
id1: usize,
id2: usize,
) -> Vec<(usize, usize)> {
let mut pairs = Vec::new();
for (a, b) in connections {
if a.tensor_id == id1 && b.tensor_id == id2 {
pairs.push((a.index, b.index));
} else if a.tensor_id == id2 && b.tensor_id == id1 {
pairs.push((b.index, a.index));
}
}
pairs
}
pub(crate) fn contract_pair_multi(
a: &Tensor,
b: &Tensor,
shared: &[(usize, usize)],
) -> QuantRS2Result<Tensor> {
for &(ax_a, ax_b) in shared {
if ax_a >= a.rank || ax_b >= b.rank {
return Err(QuantRS2Error::CircuitValidationFailed(format!(
"contract_pair_multi: axis out of range ({ax_a}, {ax_b})"
)));
}
if a.dimensions[ax_a] != b.dimensions[ax_b] {
return Err(QuantRS2Error::CircuitValidationFailed(format!(
"contract_pair_multi: dimension mismatch {} vs {}",
a.dimensions[ax_a], b.dimensions[ax_b]
)));
}
}
let a_contracted: HashSet<usize> = shared.iter().map(|&(ax, _)| ax).collect();
let b_contracted: HashSet<usize> = shared.iter().map(|&(_, ax)| ax).collect();
let a_free: Vec<usize> = (0..a.rank)
.filter(|ax| !a_contracted.contains(ax))
.collect();
let b_free: Vec<usize> = (0..b.rank)
.filter(|ax| !b_contracted.contains(ax))
.collect();
let mut result_dims: Vec<usize> = a_free.iter().map(|&ax| a.dimensions[ax]).collect();
result_dims.extend(b_free.iter().map(|&ax| b.dimensions[ax]));
let result_is_scalar = result_dims.is_empty();
let result_shape = if result_is_scalar {
IxDyn(&[1usize])
} else {
IxDyn(result_dims.as_slice())
};
let mut result_data = ArrayD::<Complex64>::zeros(result_shape);
for (a_idx, a_val) in a.data.indexed_iter() {
let a_raw = a_idx.slice();
for (b_idx, b_val) in b.data.indexed_iter() {
let b_raw = b_idx.slice();
if shared
.iter()
.any(|&(ax_a, ax_b)| a_raw[ax_a] != b_raw[ax_b])
{
continue;
}
let mut res_idx: Vec<usize> = a_free.iter().map(|&ax| a_raw[ax]).collect();
res_idx.extend(b_free.iter().map(|&ax| b_raw[ax]));
let target = if result_is_scalar {
&mut result_data[IxDyn(&[0usize])]
} else {
&mut result_data[IxDyn(res_idx.as_slice())]
};
*target += *a_val * *b_val;
}
}
let final_data = if result_is_scalar {
let scalar = result_data[IxDyn(&[0usize])];
ArrayD::from_elem(IxDyn(&[]), scalar)
} else {
result_data
};
Ok(Tensor::new(final_data))
}
struct LabeledTensor {
tensor: Tensor,
labels: Vec<usize>,
}
pub fn contract_network_along_path(
tensors: &mut HashMap<usize, Tensor>,
connections: &mut Vec<(super::tensor::TensorIndex, super::tensor::TensorIndex)>,
path: &ContractionPath,
next_id: &mut usize,
) -> QuantRS2Result<Tensor> {
let _ = next_id;
if tensors.is_empty() {
return Err(QuantRS2Error::CircuitValidationFailed(
"contract_network_along_path: empty tensor network".to_string(),
));
}
let mut next_label = 0usize;
let mut working: HashMap<usize, LabeledTensor> = HashMap::new();
for (&id, tensor) in tensors.iter() {
let labels: Vec<usize> = (0..tensor.rank)
.map(|_| {
let label = next_label;
next_label += 1;
label
})
.collect();
working.insert(
id,
LabeledTensor {
tensor: tensor.clone(),
labels,
},
);
}
for (a, b) in connections.iter() {
let bond_label = next_label;
next_label += 1;
if let Some(lt) = working.get_mut(&a.tensor_id) {
if a.index < lt.labels.len() {
lt.labels[a.index] = bond_label;
}
}
if let Some(lt) = working.get_mut(&b.tensor_id) {
if b.index < lt.labels.len() {
lt.labels[b.index] = bond_label;
}
}
}
for &(id1, id2) in path.steps() {
if id1 == id2 {
continue;
}
let lt1 = working.remove(&id1).ok_or_else(|| {
QuantRS2Error::CircuitValidationFailed(format!(
"contract_network_along_path: tensor {id1} missing from path step"
))
})?;
let lt2 = working.remove(&id2).ok_or_else(|| {
QuantRS2Error::CircuitValidationFailed(format!(
"contract_network_along_path: tensor {id2} missing from path step"
))
})?;
let merged = contract_labeled(<1, <2)?;
working.insert(id1, merged);
}
let mut remaining: Vec<LabeledTensor> = working.into_values().collect();
let mut acc = remaining.remove(0);
for lt in remaining {
acc = contract_labeled(&acc, <)?;
}
Ok(acc.tensor)
}
fn contract_labeled(a: &LabeledTensor, b: &LabeledTensor) -> QuantRS2Result<LabeledTensor> {
let mut shared_pairs = Vec::new();
for (ai, &la) in a.labels.iter().enumerate() {
for (bi, &lb) in b.labels.iter().enumerate() {
if la == lb {
shared_pairs.push((ai, bi));
}
}
}
let merged = contract_pair_multi(&a.tensor, &b.tensor, &shared_pairs)?;
let a_contracted: HashSet<usize> = shared_pairs.iter().map(|&(ai, _)| ai).collect();
let b_contracted: HashSet<usize> = shared_pairs.iter().map(|&(_, bi)| bi).collect();
let mut labels: Vec<usize> = a
.labels
.iter()
.enumerate()
.filter(|&(i, _)| !a_contracted.contains(&i))
.map(|(_, &l)| l)
.collect();
labels.extend(
b.labels
.iter()
.enumerate()
.filter(|&(i, _)| !b_contracted.contains(&i))
.map(|(_, &l)| l),
);
Ok(LabeledTensor {
tensor: merged,
labels,
})
}