use crate::device::PhysicalQubit;
use crate::device::error::TopologyError;
use rustworkx_core::petgraph::Direction;
use rustworkx_core::petgraph::prelude::{NodeIndex, StableDiGraph, StableGraph};
use rustworkx_core::petgraph::visit::{EdgeRef, IntoEdgeReferences};
use std::collections::{BTreeSet, HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct Topology {
node_indices: HashMap<PhysicalQubit, NodeIndex>,
graph: StableGraph<PhysicalQubit, String>,
}
impl Topology {
pub fn new(
qubits: Vec<PhysicalQubit>,
coupling_map: Vec<(PhysicalQubit, PhysicalQubit, String)>,
) -> Result<Self, TopologyError> {
let mut topology = Self {
node_indices: HashMap::new(),
graph: StableDiGraph::<PhysicalQubit, String>::new(),
};
topology.add_qubits(qubits)?;
topology.add_couplings(coupling_map)?;
Ok(topology)
}
pub fn line(qubits: Vec<PhysicalQubit>) -> Result<Self, TopologyError> {
let couplings = qubits
.windows(2)
.map(|qs| (qs[0], qs[1], String::new()))
.collect();
Self::new(qubits, couplings)
}
pub fn graph(&self) -> &StableGraph<PhysicalQubit, String> {
&self.graph
}
pub fn num_qubits(&self) -> usize {
self.graph.node_indices().count()
}
pub fn num_couplings(&self) -> usize {
self.graph.edge_indices().count()
}
pub fn qubits(&self) -> impl Iterator<Item = PhysicalQubit> + '_ {
self.graph.node_indices().map(|i| self.graph[i])
}
pub fn add_qubits(
&mut self,
qubits: impl IntoIterator<Item = PhysicalQubit>,
) -> Result<(), TopologyError> {
let qubits: Vec<PhysicalQubit> = qubits.into_iter().collect();
let mut seen = HashSet::with_capacity(qubits.len());
for qubit in &qubits {
if self.node_indices.contains_key(qubit) || !seen.insert(*qubit) {
return Err(TopologyError::QubitAlreadyExists(*qubit));
}
}
for qubit in qubits {
let node_index = self.graph.add_node(qubit);
self.node_indices.insert(qubit, node_index);
}
Ok(())
}
pub fn add_couplings(
&mut self,
couplings: impl IntoIterator<Item = (PhysicalQubit, PhysicalQubit, String)>,
) -> Result<(), TopologyError> {
let couplings: Vec<(PhysicalQubit, PhysicalQubit, String)> =
couplings.into_iter().collect();
let mut seen = HashSet::with_capacity(couplings.len());
for (control, target, _) in &couplings {
if !self.node_indices.contains_key(control) {
return Err(TopologyError::QubitNotFound(*control));
}
if !self.node_indices.contains_key(target) {
return Err(TopologyError::QubitNotFound(*target));
}
if control == target {
return Err(TopologyError::SelfCoupling { qubit: *control });
}
if !seen.insert((*control, *target)) {
return Err(TopologyError::CouplingAlreadyExists {
control: *control,
target: *target,
});
}
let c_idx = self.node_indices[control];
let t_idx = self.node_indices[target];
if self.graph.find_edge(c_idx, t_idx).is_some() {
return Err(TopologyError::CouplingAlreadyExists {
control: *control,
target: *target,
});
}
}
for (control, target, name) in couplings {
let c_idx = self.node_indices[&control];
let t_idx = self.node_indices[&target];
self.graph.add_edge(c_idx, t_idx, name);
}
Ok(())
}
pub fn remove_qubits(
&mut self,
qubits: impl IntoIterator<Item = PhysicalQubit>,
) -> Result<(), TopologyError> {
let qubits: Vec<PhysicalQubit> = qubits.into_iter().collect();
let mut seen = HashSet::with_capacity(qubits.len());
for qubit in &qubits {
if !self.node_indices.contains_key(qubit) {
return Err(TopologyError::QubitNotFound(*qubit));
}
if !seen.insert(*qubit) {
return Err(TopologyError::DuplicateQubitRemoval(*qubit));
}
}
for qubit in qubits {
if let Some(node_index) = self.node_indices.remove(&qubit) {
self.graph.remove_node(node_index);
}
}
Ok(())
}
pub fn remove_couplings(
&mut self,
couplings: impl IntoIterator<Item = (PhysicalQubit, PhysicalQubit)>,
) -> Result<(), TopologyError> {
let collected: Vec<_> = couplings.into_iter().collect();
let mut seen = HashSet::with_capacity(collected.len());
for (control, target) in &collected {
if !self.node_indices.contains_key(control) {
return Err(TopologyError::QubitNotFound(*control));
}
if !self.node_indices.contains_key(target) {
return Err(TopologyError::QubitNotFound(*target));
}
if !seen.insert((*control, *target)) {
return Err(TopologyError::DuplicateCouplingRemoval {
control: *control,
target: *target,
});
}
let c_idx = self.node_indices[control];
let t_idx = self.node_indices[target];
if self.graph.find_edge(c_idx, t_idx).is_none() {
return Err(TopologyError::CouplingNotFound {
control: *control,
target: *target,
});
}
}
for (control, target) in collected {
let c_idx = self.node_indices[&control];
let t_idx = self.node_indices[&target];
if let Some(edge_idx) = self.graph.find_edge(c_idx, t_idx) {
self.graph.remove_edge(edge_idx);
}
}
Ok(())
}
pub fn supports_directed_coupling(
&self,
control: PhysicalQubit,
target: PhysicalQubit,
) -> bool {
if let (Some(&c_idx), Some(&t_idx)) = (
self.node_indices.get(&control),
self.node_indices.get(&target),
) {
self.graph.find_edge(c_idx, t_idx).is_some()
} else {
false
}
}
pub fn supports_coupling_either_direction(&self, a: PhysicalQubit, b: PhysicalQubit) -> bool {
self.supports_directed_coupling(a, b) || self.supports_directed_coupling(b, a)
}
pub fn successors(&self, qubit: PhysicalQubit) -> impl Iterator<Item = PhysicalQubit> + '_ {
self.node_indices
.get(&qubit)
.into_iter()
.flat_map(|&node_idx| self.graph.edges(node_idx).map(|e| self.graph[e.target()]))
}
pub fn predecessors(&self, qubit: PhysicalQubit) -> impl Iterator<Item = PhysicalQubit> + '_ {
self.node_indices
.get(&qubit)
.into_iter()
.flat_map(|&node_idx| {
self.graph
.edges_directed(node_idx, Direction::Incoming)
.map(|e| self.graph[e.source()])
})
}
pub fn neighbors_undirected(
&self,
qubit: PhysicalQubit,
) -> impl Iterator<Item = PhysicalQubit> + '_ {
let mut neighbors = BTreeSet::new();
neighbors.extend(self.successors(qubit));
neighbors.extend(self.predecessors(qubit));
neighbors.into_iter()
}
pub fn undirected_edges(&self) -> impl Iterator<Item = (PhysicalQubit, PhysicalQubit)> + '_ {
self.graph
.edge_references()
.map(|edge| {
let source = self.graph[edge.source()];
let target = self.graph[edge.target()];
if source <= target {
(source, target)
} else {
(target, source)
}
})
.collect::<BTreeSet<_>>()
.into_iter()
}
pub fn get_coupling_name(
&self,
control: PhysicalQubit,
target: PhysicalQubit,
) -> Option<String> {
let c_idx = self.node_indices.get(&control)?;
let t_idx = self.node_indices.get(&target)?;
self.graph
.find_edge(*c_idx, *t_idx)
.map(|e| self.graph[e].clone())
}
pub fn contains_qubit(&self, qubit: &PhysicalQubit) -> bool {
self.node_indices.contains_key(qubit)
}
pub fn out_degree(&self, qubit: &PhysicalQubit) -> usize {
if let Some(&node_idx) = self.node_indices.get(qubit) {
self.graph.edges(node_idx).count()
} else {
0
}
}
pub fn in_degree(&self, qubit: &PhysicalQubit) -> usize {
if let Some(&node_idx) = self.node_indices.get(qubit) {
self.graph
.edges_directed(node_idx, Direction::Incoming)
.count()
} else {
0
}
}
}
#[cfg(test)]
#[path = "./topology_test.rs"]
mod topology_test;