use crate::circuit::bit::Qubit;
use crate::circuit::circuit_impl::Circuit;
use crate::circuit::circuit_param::ParameterValue;
use crate::circuit::error::CircuitError;
use crate::circuit::parameter::Parameter;
use crate::qis::error::QisError;
use crate::qis::hamiltonian::Hamiltonian;
use crate::qis::pauli::{Pauli, PauliString};
use num_complex::Complex64;
use rand::SeedableRng;
use rand::rngs::StdRng;
use rand::seq::SliceRandom;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TrotterMode {
FirstOrder,
SecondOrder,
Randomized(u64),
}
pub trait PauliEvolution {
fn pauli_evolution(
&mut self,
pauli: &PauliString,
angle: impl Into<ParameterValue>,
qubits: &[Qubit],
) -> Result<(), CircuitError>;
}
impl PauliEvolution for Circuit {
fn pauli_evolution(
&mut self,
pauli: &PauliString,
angle: impl Into<ParameterValue>,
qubits: &[Qubit],
) -> Result<(), CircuitError> {
if qubits.len() != pauli.num_qubits {
return Err(CircuitError::QubitCountMismatch {
expected: pauli.num_qubits,
actual: qubits.len(),
});
}
let phase_complex = pauli.phase.to_complex();
if phase_complex.im.abs() > 1e-10 {
return Err(CircuitError::InvalidOperation(
"Pauli string phase must be Hermitian (±1)".to_string(),
));
}
let angle_param: ParameterValue = angle.into();
let adjusted_angle = if phase_complex.re < 0.0 {
angle_param.scaled(-1.0)
} else {
angle_param
};
let mut non_identity_positions: Vec<(usize, Pauli)> = Vec::new();
for i in 0..pauli.num_qubits {
let x_bit = pauli.x[i];
let z_bit = pauli.z[i];
let pauli_op = match (x_bit, z_bit) {
(false, false) => Pauli::I,
(true, false) => Pauli::X,
(true, true) => Pauli::Y,
(false, true) => Pauli::Z,
};
if pauli_op != Pauli::I {
non_identity_positions.push((i, pauli_op));
}
}
if non_identity_positions.is_empty() {
let global_phase_param = adjusted_angle.scaled(-0.5);
let current_phase = self.global_phase();
let global_phase_param = Parameter::from(global_phase_param);
let new_phase = current_phase + global_phase_param;
self.set_global_phase(new_phase);
return Ok(());
}
let mut transformations: Vec<(Qubit, Pauli)> =
Vec::with_capacity(non_identity_positions.len());
for (idx, pauli_op) in &non_identity_positions {
let qubit = qubits[*idx];
transformations.push((qubit, *pauli_op));
match pauli_op {
Pauli::X => {
self.h(qubit)?;
}
Pauli::Y => {
self.sdg(qubit)?;
self.h(qubit)?;
}
Pauli::Z => {
}
Pauli::I => unreachable!(),
}
}
let num_non_identity = non_identity_positions.len();
let last_qubit = qubits[non_identity_positions[num_non_identity - 1].0];
for i in 0..num_non_identity - 1 {
let curr_qubit = qubits[non_identity_positions[i].0];
let next_qubit = qubits[non_identity_positions[i + 1].0];
self.cx(curr_qubit, next_qubit)?;
}
self.rz(last_qubit, adjusted_angle)?;
for i in (0..num_non_identity - 1).rev() {
let curr_qubit = qubits[non_identity_positions[i].0];
let next_qubit = qubits[non_identity_positions[i + 1].0];
self.cx(curr_qubit, next_qubit)?;
}
for (qubit, pauli_op) in transformations.iter().rev() {
match pauli_op {
Pauli::X => {
self.h(*qubit)?;
}
Pauli::Y => {
self.h(*qubit)?;
self.s(*qubit)?;
}
Pauli::Z => {
}
Pauli::I => unreachable!(),
}
}
Ok(())
}
}
pub(crate) fn trotter_first_order_core(
circuit: &mut Circuit,
terms: &[(PauliString, Complex64)],
t: ParameterValue,
steps: usize,
qubits: &[Qubit],
mut rng: Option<&mut StdRng>,
) -> Result<(), CircuitError> {
let angles: Vec<ParameterValue> = terms
.iter()
.map(|(_, coeff)| {
let factor = 2.0 * coeff.re / steps as f64;
if !factor.is_finite() {
return Err(CircuitError::InvalidOperation(format!(
"Hamiltonian evolution scale factor must be finite, got {factor}"
)));
}
Ok(t.scaled(factor))
})
.collect::<Result<_, _>>()?;
let mut indices: Vec<usize> = (0..terms.len()).collect();
for _ in 0..steps {
if let Some(ref mut r) = rng {
indices.shuffle(*r);
}
for &idx in &indices {
let (pauli, _) = &terms[idx];
circuit
.pauli_evolution(pauli, angles[idx].clone(), qubits)
.map_err(|e| {
CircuitError::InvalidOperation(format!("Failed to add Pauli evolution: {}", e))
})?;
}
}
Ok(())
}
pub(crate) fn trotter_second_order_core(
circuit: &mut Circuit,
terms: &[(PauliString, Complex64)],
t: ParameterValue,
steps: usize,
qubits: &[Qubit],
) -> Result<(), CircuitError> {
let angles: Vec<ParameterValue> = terms
.iter()
.map(|(_, coeff)| {
let factor = coeff.re / steps as f64;
if !factor.is_finite() {
return Err(CircuitError::InvalidOperation(format!(
"Hamiltonian evolution scale factor must be finite, got {factor}"
)));
}
Ok(t.scaled(factor))
})
.collect::<Result<_, _>>()?;
for _ in 0..steps {
for (idx, (pauli, _)) in terms.iter().enumerate() {
circuit
.pauli_evolution(pauli, angles[idx].clone(), qubits)
.map_err(|e| {
CircuitError::InvalidOperation(format!("Failed to add Pauli evolution: {}", e))
})?;
}
for (idx, (pauli, _)) in terms.iter().enumerate().rev() {
circuit
.pauli_evolution(pauli, angles[idx].clone(), qubits)
.map_err(|e| {
CircuitError::InvalidOperation(format!("Failed to add Pauli evolution: {}", e))
})?;
}
}
Ok(())
}
impl Hamiltonian {
pub fn to_trotter_circuit(
&self,
time: f64,
steps: usize,
mode: TrotterMode,
) -> Result<Circuit, QisError> {
if steps == 0 {
return Err(QisError::InvalidParameterValue(
"Trotter steps must be greater than 0".to_string(),
));
}
if self.terms.is_empty() {
return Err(QisError::InvalidParameterValue(
"Cannot create Trotter circuit from empty Hamiltonian".to_string(),
));
}
let mut simplified_h = self.clone();
simplified_h.simplify_with_tolerance(0.0);
for (_, coeff) in &simplified_h.terms {
if coeff.im.abs() > 1e-10 {
return Err(QisError::NotHermitian);
}
}
let mut circuit = Circuit::new(simplified_h.num_qubits);
let qubits: Vec<Qubit> = circuit.qubits();
let t = ParameterValue::Fixed(time);
let err_map = |e: CircuitError| {
QisError::UnsupportedOperation(format!("Failed to add Pauli evolution: {}", e))
};
match mode {
TrotterMode::FirstOrder => {
trotter_first_order_core(
&mut circuit,
&simplified_h.terms,
t,
steps,
&qubits,
None,
)
.map_err(err_map)?;
}
TrotterMode::Randomized(seed) => {
let mut rng = StdRng::seed_from_u64(seed);
trotter_first_order_core(
&mut circuit,
&simplified_h.terms,
t,
steps,
&qubits,
Some(&mut rng),
)
.map_err(err_map)?;
}
TrotterMode::SecondOrder => {
trotter_second_order_core(&mut circuit, &simplified_h.terms, t, steps, &qubits)
.map_err(err_map)?;
}
}
Ok(circuit)
}
pub fn to_evolution_circuit(
&self,
time: f64,
steps: usize,
mode: TrotterMode,
) -> Result<Circuit, QisError> {
if steps == 0 {
return Err(QisError::InvalidParameterValue(
"Trotter steps must be greater than 0".to_string(),
));
}
if self.terms.is_empty() {
return Err(QisError::InvalidParameterValue(
"Cannot create evolution circuit from empty Hamiltonian".to_string(),
));
}
let mut simplified_h = self.clone();
simplified_h.simplify_with_tolerance(0.0);
for (_, coeff) in &simplified_h.terms {
if coeff.im.abs() > 1e-10 {
return Err(QisError::NotHermitian);
}
}
let mut circuit = Circuit::new(simplified_h.num_qubits);
let qubits: Vec<Qubit> = circuit.qubits();
let err_map = |e: CircuitError| {
QisError::UnsupportedOperation(format!("Failed to add Pauli evolution: {}", e))
};
if simplified_h.all_terms_commute() {
for (pauli, coeff) in &simplified_h.terms {
circuit
.pauli_evolution(pauli, 2.0 * coeff.re * time, &qubits)
.map_err(err_map)?;
}
} else {
let t = ParameterValue::Fixed(time);
match mode {
TrotterMode::FirstOrder => {
trotter_first_order_core(
&mut circuit,
&simplified_h.terms,
t,
steps,
&qubits,
None,
)
.map_err(err_map)?;
}
TrotterMode::Randomized(seed) => {
let mut rng = StdRng::seed_from_u64(seed);
trotter_first_order_core(
&mut circuit,
&simplified_h.terms,
t,
steps,
&qubits,
Some(&mut rng),
)
.map_err(err_map)?;
}
TrotterMode::SecondOrder => {
trotter_second_order_core(&mut circuit, &simplified_h.terms, t, steps, &qubits)
.map_err(err_map)?;
}
}
}
Ok(circuit)
}
}
#[cfg(test)]
#[path = "evolution_test.rs"]
mod evolution_test;