use super::traits::Ansatz;
use crate::circuit::Parameter;
use crate::circuit::ParameterValue;
use crate::circuit::circuit_impl::Circuit;
use crate::circuit::error::CircuitError;
use crate::circuit::gate::StandardGate;
use crate::circuit::{Instruction, Qubit};
fn k_combinations(n: usize, k: usize) -> Vec<Vec<usize>> {
let mut result = Vec::new();
let mut combo = Vec::with_capacity(k);
k_combinations_helper(0, n, k, &mut combo, &mut result);
result
}
fn k_combinations_helper(
start: usize,
n: usize,
remaining: usize,
combo: &mut Vec<usize>,
result: &mut Vec<Vec<usize>>,
) {
if remaining == 0 {
result.push(combo.clone());
return;
}
for i in start..=(n - remaining) {
combo.push(i);
k_combinations_helper(i + 1, n, remaining - 1, combo, result);
combo.pop();
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EntanglementTopology {
Linear,
Circular,
Full,
Custom(Vec<(usize, usize)>),
}
impl EntanglementTopology {
pub fn generate_k_tuples(&self, k: usize, num_qubits: usize) -> Vec<Vec<usize>> {
if k == 0 || num_qubits == 0 || k > num_qubits {
return vec![];
}
if k == 1 {
return (0..num_qubits).map(|i| vec![i]).collect();
}
if k == 2 {
return self
.generate_pairs(num_qubits)
.into_iter()
.map(|(a, b)| vec![a, b])
.collect();
}
match self {
EntanglementTopology::Linear => {
(0..=num_qubits - k).map(|i| (i..i + k).collect()).collect()
}
EntanglementTopology::Circular => (0..num_qubits)
.map(|i| (0..k).map(|j| (i + j) % num_qubits).collect())
.collect(),
EntanglementTopology::Full | EntanglementTopology::Custom(_) => {
k_combinations(num_qubits, k)
}
}
}
pub fn generate_pairs(&self, num_qubits: usize) -> Vec<(usize, usize)> {
match self {
EntanglementTopology::Linear => (0..num_qubits.saturating_sub(1))
.map(|i| (i, i + 1))
.collect(),
EntanglementTopology::Circular => {
let mut p: Vec<(usize, usize)> = (0..num_qubits.saturating_sub(1))
.map(|i| (i, i + 1))
.collect();
if num_qubits > 2 {
p.push((num_qubits - 1, 0));
}
p
}
EntanglementTopology::Full => {
let mut p = Vec::new();
for i in 0..num_qubits {
for j in (i + 1)..num_qubits {
p.push((i, j));
}
}
p
}
EntanglementTopology::Custom(pairs) => pairs.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct TwoLocal {
num_qubits: usize,
reps: usize,
rotation_gates: Vec<StandardGate>,
entanglement_gate: StandardGate,
entanglement: EntanglementTopology,
skip_final_rotation_layer: bool,
}
impl TwoLocal {
pub fn new(num_qubits: usize) -> Self {
Self {
num_qubits,
reps: 3,
rotation_gates: vec![StandardGate::RY],
entanglement_gate: StandardGate::CX,
entanglement: EntanglementTopology::Linear,
skip_final_rotation_layer: false,
}
}
pub fn reps(mut self, reps: usize) -> Self {
self.reps = reps;
self
}
pub fn rotation_gates(mut self, gates: Vec<StandardGate>) -> Self {
self.rotation_gates = gates;
self
}
pub fn entanglement_gate(mut self, gate: StandardGate) -> Self {
self.entanglement_gate = gate;
self
}
pub fn entanglement(mut self, topology: EntanglementTopology) -> Self {
self.entanglement = topology;
self
}
pub fn skip_final_rotation_layer(mut self, skip: bool) -> Self {
self.skip_final_rotation_layer = skip;
self
}
}
impl Ansatz for TwoLocal {
fn validate(&self) -> Result<(), CircuitError> {
if self.num_qubits == 0 {
return Err(CircuitError::InvalidOperation(
"TwoLocal requires at least 1 qubit".to_string(),
));
}
for gate in &self.rotation_gates {
if !matches!(gate, StandardGate::RX | StandardGate::RY | StandardGate::RZ) {
return Err(CircuitError::InvalidOperation(format!(
"Rotation gates must be RX, RY, or RZ, got {:?}",
gate
)));
}
}
if !matches!(
self.entanglement_gate,
StandardGate::CX | StandardGate::CY | StandardGate::CZ
) {
return Err(CircuitError::InvalidOperation(format!(
"Entanglement gate must be CX, CY, or CZ, got {:?}",
self.entanglement_gate
)));
}
if let EntanglementTopology::Custom(pairs) = &self.entanglement {
use std::collections::HashSet;
let mut seen = HashSet::new();
for (c, t) in pairs {
if c == t {
return Err(CircuitError::InvalidOperation(format!(
"EntanglementTopology::Custom contains self-loop ({}, {})",
c, t
)));
}
if *c >= self.num_qubits || *t >= self.num_qubits {
return Err(CircuitError::InvalidOperation(format!(
"EntanglementTopology::Custom contains out-of-bounds index: ({}, {}) for {} qubits",
c, t, self.num_qubits
)));
}
let edge = if c < t { (*c, *t) } else { (*t, *c) };
if !seen.insert(edge) {
return Err(CircuitError::InvalidOperation(format!(
"EntanglementTopology::Custom contains duplicate edge ({}, {})",
c, t
)));
}
}
}
Ok(())
}
fn build_circuit(&self, prefix: &str) -> Result<Circuit, CircuitError> {
self.validate()?;
let mut circuit = Circuit::new(self.num_qubits);
let mut param_idx = 0;
let num_layers = if self.skip_final_rotation_layer {
self.reps
} else {
self.reps + 1
};
for layer in 0..num_layers {
for q in 0..self.num_qubits {
for gate in &self.rotation_gates {
let param_name = format!("{}_{}", prefix, param_idx);
let param = Parameter::try_from(param_name.as_str())
.map_err(|_| CircuitError::InvalidParameterValue(param_idx, f64::NAN))?;
circuit.append(
Instruction::Standard(*gate),
vec![Qubit::new(q as u32)],
vec![ParameterValue::Param(param)],
None,
)?;
param_idx += 1;
}
}
if layer < self.reps {
let pairs = self.entanglement.generate_pairs(self.num_qubits);
for (c, t) in pairs {
circuit.append(
Instruction::Standard(self.entanglement_gate),
vec![Qubit::new(c as u32), Qubit::new(t as u32)],
vec![],
None,
)?;
}
}
}
Ok(circuit)
}
fn num_parameters(&self) -> usize {
let layers = if self.skip_final_rotation_layer {
self.reps
} else {
self.reps + 1
};
layers * self.num_qubits * self.rotation_gates.len()
}
fn num_qubits(&self) -> usize {
self.num_qubits
}
}