use super::traits::Ansatz;
use super::two_local::EntanglementTopology;
use crate::circuit::Instruction;
use crate::circuit::Parameter;
use crate::circuit::ParameterValue;
use crate::circuit::bit::Qubit;
use crate::circuit::circuit_impl::Circuit;
use crate::circuit::error::CircuitError;
use crate::circuit::gate::StandardGate;
use crate::qis::evolution::PauliEvolution;
use crate::qis::pauli::{Pauli, PauliString};
#[derive(Debug, Clone)]
pub struct AngleEncoding {
num_qubits: usize,
rotation_gate: StandardGate,
}
impl AngleEncoding {
pub fn new(num_qubits: usize, rotation_gate: StandardGate) -> Self {
Self {
num_qubits,
rotation_gate,
}
}
}
impl Ansatz for AngleEncoding {
fn validate(&self) -> Result<(), CircuitError> {
if self.num_qubits == 0 {
return Err(CircuitError::InvalidOperation(
"AngleEncoding requires at least 1 qubit".to_string(),
));
}
if !matches!(
self.rotation_gate,
StandardGate::RX | StandardGate::RY | StandardGate::RZ | StandardGate::Phase
) {
return Err(CircuitError::InvalidOperation(format!(
"AngleEncoding rotation_gate must be a single-parameter single-qubit gate \
(RX, RY, RZ, or Phase), got {:?}",
self.rotation_gate
)));
}
Ok(())
}
fn build_circuit(&self, prefix: &str) -> Result<Circuit, CircuitError> {
self.validate()?;
let mut circuit = Circuit::new(self.num_qubits);
for q in 0..self.num_qubits {
let param_name = format!("{}_{}", prefix, q);
let param = Parameter::try_from(param_name.as_str())
.map_err(|_| CircuitError::InvalidParameterValue(q, f64::NAN))?;
circuit.append(
Instruction::Standard(self.rotation_gate),
vec![Qubit::new(q as u32)],
vec![ParameterValue::Param(param)],
None,
)?;
}
Ok(circuit)
}
fn num_parameters(&self) -> usize {
self.num_qubits
}
fn num_qubits(&self) -> usize {
self.num_qubits
}
}
#[derive(Debug, Clone)]
pub struct BasisEncoding {
bits: Vec<bool>,
}
impl BasisEncoding {
pub fn new(bits: Vec<bool>) -> Self {
Self { bits }
}
}
impl Ansatz for BasisEncoding {
fn validate(&self) -> Result<(), CircuitError> {
if self.bits.is_empty() {
return Err(CircuitError::InvalidOperation(
"BasisEncoding requires at least 1 bit".to_string(),
));
}
Ok(())
}
fn build_circuit(&self, _prefix: &str) -> Result<Circuit, CircuitError> {
self.validate()?;
let mut circuit = Circuit::new(self.bits.len());
for (idx, bit) in self.bits.iter().enumerate() {
if *bit {
circuit.x(Qubit::new(idx as u32))?;
}
}
Ok(circuit)
}
fn num_parameters(&self) -> usize {
0
}
fn num_qubits(&self) -> usize {
self.bits.len()
}
}
#[derive(Debug, Clone)]
pub struct ZFeatureMap {
num_qubits: usize,
reps: usize,
}
impl ZFeatureMap {
pub fn new(num_qubits: usize) -> Self {
Self {
num_qubits,
reps: 2,
}
}
pub fn reps(mut self, reps: usize) -> Self {
self.reps = reps;
self
}
}
impl Ansatz for ZFeatureMap {
fn validate(&self) -> Result<(), CircuitError> {
if self.num_qubits == 0 {
return Err(CircuitError::InvalidOperation(
"ZFeatureMap requires at least 1 qubit".to_string(),
));
}
Ok(())
}
fn build_circuit(&self, prefix: &str) -> Result<Circuit, CircuitError> {
self.validate()?;
let mut circuit = Circuit::new(self.num_qubits);
let mut x_params = Vec::with_capacity(self.num_qubits);
for i in 0..self.num_qubits {
let param_name = format!("{}_{}", prefix, i);
let param = Parameter::try_from(param_name.as_str())
.map_err(|_| CircuitError::InvalidParameterValue(i, f64::NAN))?;
x_params.push(param);
}
for _layer in 0..self.reps {
for q in 0..self.num_qubits {
circuit.h(Qubit::new(q as u32))?;
}
for (i, param) in x_params.iter().enumerate() {
circuit.rz(
Qubit::new(i as u32),
ParameterValue::Param(param.clone() * 2.0),
)?;
}
}
Ok(circuit)
}
fn num_parameters(&self) -> usize {
if self.reps == 0 {
return 0;
}
self.num_qubits
}
fn num_qubits(&self) -> usize {
self.num_qubits
}
}
#[derive(Debug, Clone)]
pub struct IQPFeatureMap {
inner: ZZFeatureMap,
}
impl IQPFeatureMap {
pub fn new(num_qubits: usize) -> Self {
Self {
inner: ZZFeatureMap::new(num_qubits),
}
}
pub fn reps(mut self, reps: usize) -> Self {
self.inner = self.inner.reps(reps);
self
}
pub fn entanglement(mut self, topology: EntanglementTopology) -> Self {
self.inner = self.inner.entanglement(topology);
self
}
}
impl Ansatz for IQPFeatureMap {
fn validate(&self) -> Result<(), CircuitError> {
self.inner.validate()
}
fn build_circuit(&self, prefix: &str) -> Result<Circuit, CircuitError> {
self.inner.build_circuit(prefix)
}
fn num_parameters(&self) -> usize {
self.inner.num_parameters()
}
fn num_qubits(&self) -> usize {
self.inner.num_qubits()
}
}
#[derive(Debug, Clone)]
pub struct ZZFeatureMap {
num_qubits: usize,
reps: usize,
entanglement: EntanglementTopology,
}
impl ZZFeatureMap {
pub fn new(num_qubits: usize) -> Self {
Self {
num_qubits,
reps: 2,
entanglement: EntanglementTopology::Full,
}
}
pub fn reps(mut self, reps: usize) -> Self {
self.reps = reps;
self
}
pub fn entanglement(mut self, topology: EntanglementTopology) -> Self {
self.entanglement = topology;
self
}
}
impl Ansatz for ZZFeatureMap {
fn validate(&self) -> Result<(), CircuitError> {
if self.num_qubits == 0 {
return Err(CircuitError::InvalidOperation(
"ZZFeatureMap requires at least 1 qubit".to_string(),
));
}
if let EntanglementTopology::Custom(pairs) = &self.entanglement {
use std::collections::HashSet;
let mut seen: HashSet<(usize, usize)> = HashSet::new();
for (i, j) in pairs {
if *i >= self.num_qubits || *j >= self.num_qubits {
return Err(CircuitError::InvalidOperation(format!(
"Custom entanglement topology contains out-of-bounds index: \
({}, {}) for {} qubits",
i, j, self.num_qubits
)));
}
if i == j {
return Err(CircuitError::InvalidOperation(format!(
"Custom entanglement topology contains self-loop ({}, {})",
i, j
)));
}
let edge = if i < j { (*i, *j) } else { (*j, *i) };
if !seen.insert(edge) {
return Err(CircuitError::InvalidOperation(format!(
"Custom entanglement topology contains duplicate edge ({}, {})",
i, j
)));
}
}
}
Ok(())
}
fn build_circuit(&self, prefix: &str) -> Result<Circuit, CircuitError> {
self.validate()?;
let mut circuit = Circuit::new(self.num_qubits);
let qubits = circuit.qubits();
let mut x_params = Vec::with_capacity(self.num_qubits);
for i in 0..self.num_qubits {
let param_name = format!("{}_{}", prefix, i);
let param = Parameter::try_from(param_name.as_str())
.map_err(|_| CircuitError::InvalidParameterValue(i, f64::NAN))?;
x_params.push(param);
}
let pairs = self.entanglement.generate_pairs(self.num_qubits);
for _layer in 0..self.reps {
for q in 0..self.num_qubits {
circuit.h(Qubit::new(q as u32))?;
}
for (i, item) in x_params.iter().enumerate().take(self.num_qubits) {
let angle = item.clone() * 2.0;
circuit.rz(Qubit::new(i as u32), ParameterValue::Param(angle))?;
}
if !pairs.is_empty() {
for &(i, j) in &pairs {
if i < self.num_qubits && j < self.num_qubits {
let pi_param = Parameter::pi();
let xi = x_params[i].clone();
let xj = x_params[j].clone();
let phi_ij = (pi_param.clone() - xi) * (pi_param - xj);
let angle = phi_ij * 4.0;
let mut pauli_str = PauliString::new(self.num_qubits);
pauli_str.set_pauli(i, Pauli::Z);
pauli_str.set_pauli(j, Pauli::Z);
circuit.pauli_evolution(
&pauli_str,
ParameterValue::Param(angle),
&qubits,
)?;
}
}
}
}
Ok(circuit)
}
fn num_parameters(&self) -> usize {
if self.reps == 0 {
return 0;
}
self.num_qubits
}
fn num_qubits(&self) -> usize {
self.num_qubits
}
}
#[derive(Debug, Clone)]
pub struct PauliFeatureMap {
num_qubits: usize,
reps: usize,
paulis: Vec<(PauliString, String)>,
entanglement: EntanglementTopology,
parameter_prefix: String,
}
impl PauliFeatureMap {
pub fn new(num_qubits: usize) -> Self {
Self {
num_qubits,
reps: 2,
paulis: vec![
("Z".parse::<PauliString>().unwrap(), "Z".to_string()),
("ZZ".parse::<PauliString>().unwrap(), "ZZ".to_string()),
],
entanglement: EntanglementTopology::Full,
parameter_prefix: "x".to_string(),
}
}
pub fn reps(mut self, reps: usize) -> Self {
self.reps = reps;
self
}
pub fn paulis(mut self, paulis: Vec<(PauliString, String)>) -> Self {
self.paulis = paulis;
self
}
pub fn entanglement(mut self, topology: EntanglementTopology) -> Self {
self.entanglement = topology;
self
}
pub fn parameter_prefix(mut self, prefix: impl Into<String>) -> Self {
self.parameter_prefix = prefix.into();
self
}
}
impl Ansatz for PauliFeatureMap {
fn validate(&self) -> Result<(), CircuitError> {
if self.num_qubits == 0 {
return Err(CircuitError::InvalidOperation(
"PauliFeatureMap requires at least 1 qubit".to_string(),
));
}
for (pauli_str, _) in &self.paulis {
if pauli_str.num_qubits > self.num_qubits {
return Err(CircuitError::InvalidOperation(format!(
"Pauli string '{}' has length {} which exceeds num_qubits {}",
pauli_str, pauli_str.num_qubits, self.num_qubits
)));
}
}
if let EntanglementTopology::Custom(pairs) = &self.entanglement {
use std::collections::HashSet;
let mut seen: HashSet<(usize, usize)> = HashSet::new();
for (i, j) in pairs {
if *i >= self.num_qubits || *j >= self.num_qubits {
return Err(CircuitError::InvalidOperation(format!(
"Custom entanglement topology contains out-of-bounds index: ({}, {}) for {} qubits",
i, j, self.num_qubits
)));
}
if i == j {
return Err(CircuitError::InvalidOperation(format!(
"Custom entanglement topology contains self-loop ({}, {})",
i, j
)));
}
let edge = if i < j { (*i, *j) } else { (*j, *i) };
if !seen.insert(edge) {
return Err(CircuitError::InvalidOperation(format!(
"Custom entanglement topology contains duplicate edge ({}, {})",
i, j
)));
}
}
}
Ok(())
}
fn build_circuit(&self, prefix: &str) -> Result<Circuit, CircuitError> {
self.validate()?;
let mut circuit = Circuit::new(self.num_qubits);
let qubits = circuit.qubits();
let effective_prefix = if prefix.is_empty() {
&self.parameter_prefix
} else {
prefix
};
let mut x_params = Vec::with_capacity(self.num_qubits);
for i in 0..self.num_qubits {
let param_name = format!("{}_{}", effective_prefix, i);
let param = Parameter::try_from(param_name.as_str())
.map_err(|_| CircuitError::InvalidParameterValue(i, f64::NAN))?;
x_params.push(param);
}
for _layer in 0..self.reps {
for q in 0..self.num_qubits {
circuit.h(Qubit::new(q as u32))?;
}
for (pauli_str, _) in &self.paulis {
let support = pauli_str.support();
let k = support.len();
if k == 0 {
continue;
}
let template_ops: Vec<Pauli> = support
.iter()
.map(|&idx| pauli_str.get_pauli(idx))
.collect();
let tuples = self.entanglement.generate_k_tuples(k, self.num_qubits);
for tuple in &tuples {
let mut full_pauli = PauliString::new(self.num_qubits);
for (pos, &qubit_idx) in tuple.iter().enumerate() {
full_pauli.set_pauli(qubit_idx, template_ops[pos]);
}
let angle = if k == 1 {
x_params[tuple[0]].clone() * 2.0
} else {
let pi = Parameter::pi();
let mut product = pi.clone() - x_params[tuple[0]].clone();
for &qi in &tuple[1..] {
product = product * (pi.clone() - x_params[qi].clone());
}
product * 4.0
};
circuit.pauli_evolution(&full_pauli, ParameterValue::Param(angle), &qubits)?;
}
}
}
Ok(circuit)
}
fn num_parameters(&self) -> usize {
if self.reps == 0 {
return 0;
}
self.num_qubits
}
fn num_qubits(&self) -> usize {
self.num_qubits
}
}
#[cfg(test)]
#[path = "feature_map_test.rs"]
mod feature_map_test;