use super::traits::Ansatz;
use crate::circuit::Parameter;
use crate::circuit::circuit_impl::Circuit;
use crate::circuit::circuit_param::ParameterValue;
use crate::circuit::error::CircuitError;
use crate::qis::error::QisError;
use crate::qis::evolution::{
PauliEvolution, TrotterMode, trotter_first_order_core, trotter_second_order_core,
};
use crate::qis::hamiltonian::Hamiltonian;
use rand::SeedableRng;
use rand::rngs::StdRng;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EvolutionStrategy {
Auto {
steps: usize,
},
Exact,
Trotter {
mode: TrotterMode,
steps: usize,
},
}
#[derive(Debug, Clone)]
pub struct EvolutionInfo {
pub is_exact: bool,
pub steps: usize,
pub trotter_mode: Option<TrotterMode>,
pub all_terms_commute: bool,
pub num_terms: usize,
}
#[derive(Debug, Clone)]
pub struct PauliEvolutionAnsatz {
hamiltonian: Hamiltonian,
all_terms_commute: bool,
time_param_name: Option<String>,
strategy: EvolutionStrategy,
}
impl PauliEvolutionAnsatz {
pub fn new(hamiltonian: Hamiltonian) -> Result<Self, QisError> {
let mut h = hamiltonian;
h.simplify();
if h.terms.is_empty() {
return Err(QisError::InvalidParameterValue(
"Hamiltonian is empty after simplification; cannot build evolution circuit"
.to_string(),
));
}
for (_pauli, coeff) in &h.terms {
if coeff.im.abs() > 1e-10 {
return Err(QisError::NotHermitian);
}
}
let all_terms_commute = h.all_terms_commute();
Ok(Self {
hamiltonian: h,
all_terms_commute,
time_param_name: None,
strategy: EvolutionStrategy::Auto { steps: 1 },
})
}
pub fn with_time_param_name(mut self, name: impl Into<String>) -> Self {
let s = name.into();
self.time_param_name = if s.is_empty() { None } else { Some(s) };
self
}
pub fn with_strategy(mut self, strategy: EvolutionStrategy) -> Self {
self.strategy = strategy;
self
}
pub fn evolution_info(&self) -> EvolutionInfo {
let all_commute = self.all_terms_commute;
let (is_exact, steps, trotter_mode) = match &self.strategy {
EvolutionStrategy::Exact => (all_commute, 1, None),
EvolutionStrategy::Auto { steps } => {
if all_commute {
(true, 1, None)
} else {
(false, *steps, Some(TrotterMode::FirstOrder))
}
}
EvolutionStrategy::Trotter { mode, steps } => (all_commute, *steps, Some(*mode)),
};
EvolutionInfo {
is_exact,
steps,
trotter_mode,
all_terms_commute: all_commute,
num_terms: self.hamiltonian.terms.len(),
}
}
}
impl Ansatz for PauliEvolutionAnsatz {
fn validate(&self) -> Result<(), CircuitError> {
if self.hamiltonian.terms.is_empty() {
return Err(CircuitError::InvalidOperation(
"Hamiltonian is empty; cannot build evolution circuit".to_string(),
));
}
for (pauli, coeff) in &self.hamiltonian.terms {
if coeff.im.abs() > 1e-10 {
return Err(CircuitError::InvalidOperation(format!(
"Hamiltonian is not Hermitian: coefficient of {} has imaginary part {}",
pauli, coeff.im
)));
}
}
match &self.strategy {
EvolutionStrategy::Exact => {
if !self.all_terms_commute {
return Err(CircuitError::InvalidOperation(
"EvolutionStrategy::Exact requires all Hamiltonian terms to \
mutually commute. Use EvolutionStrategy::Auto or \
EvolutionStrategy::Trotter for non-commuting Hamiltonians."
.to_string(),
));
}
}
EvolutionStrategy::Auto { steps } => {
if *steps == 0 {
return Err(CircuitError::InvalidOperation(
"EvolutionStrategy::Auto requires steps >= 1".to_string(),
));
}
}
EvolutionStrategy::Trotter { steps, .. } => {
if *steps == 0 {
return Err(CircuitError::InvalidOperation(
"EvolutionStrategy::Trotter requires steps >= 1".to_string(),
));
}
}
}
Ok(())
}
fn build_circuit(&self, prefix: &str) -> Result<Circuit, CircuitError> {
self.validate()?;
let param_name = match &self.time_param_name {
Some(name) => name.clone(),
None => format!("{}_t", prefix),
};
validate_time_parameter_name(¶m_name)?;
let t = Parameter::try_from(param_name.as_str()).map_err(|_| {
CircuitError::InvalidOperation(format!(
"Invalid time parameter name '{param_name}': expected a single symbolic identifier"
))
})?;
let mut circuit = Circuit::new(self.hamiltonian.num_qubits);
let qubits = circuit.qubits();
let all_commute = self.all_terms_commute;
match &self.strategy {
EvolutionStrategy::Exact => {
apply_exact_evolution(&mut circuit, &self.hamiltonian, &t, &qubits)?;
}
EvolutionStrategy::Auto { steps } => {
if all_commute {
apply_exact_evolution(&mut circuit, &self.hamiltonian, &t, &qubits)?;
} else {
trotter_first_order_core(
&mut circuit,
&self.hamiltonian.terms,
ParameterValue::Param(t),
*steps,
&qubits,
None,
)?;
}
}
EvolutionStrategy::Trotter { mode, steps } => {
let n = *steps;
match mode {
TrotterMode::FirstOrder => {
trotter_first_order_core(
&mut circuit,
&self.hamiltonian.terms,
ParameterValue::Param(t),
n,
&qubits,
None,
)?;
}
TrotterMode::SecondOrder => {
trotter_second_order_core(
&mut circuit,
&self.hamiltonian.terms,
ParameterValue::Param(t),
n,
&qubits,
)?;
}
TrotterMode::Randomized(seed) => {
let mut rng = StdRng::seed_from_u64(*seed);
trotter_first_order_core(
&mut circuit,
&self.hamiltonian.terms,
ParameterValue::Param(t),
n,
&qubits,
Some(&mut rng),
)?;
}
}
}
}
Ok(circuit)
}
fn num_parameters(&self) -> usize {
1
}
fn num_qubits(&self) -> usize {
self.hamiltonian.num_qubits
}
}
fn validate_time_parameter_name(name: &str) -> Result<(), CircuitError> {
if matches!(name, "e" | "π") {
return Err(CircuitError::InvalidOperation(format!(
"Invalid time parameter name '{name}': reserved mathematical constants cannot be used"
)));
}
let parsed = Parameter::try_from(name).map_err(|_| {
CircuitError::InvalidOperation(format!(
"Invalid time parameter name '{name}': expected a single symbolic identifier"
))
})?;
let symbols = parsed.get_symbols();
if symbols.len() != 1 || !symbols.contains(name) {
return Err(CircuitError::InvalidOperation(format!(
"Invalid time parameter name '{name}': expected a single symbolic identifier"
)));
}
Ok(())
}
fn apply_exact_evolution(
circuit: &mut Circuit,
hamiltonian: &Hamiltonian,
t: &Parameter,
qubits: &[crate::circuit::bit::Qubit],
) -> Result<(), CircuitError> {
for (pauli, coeff) in &hamiltonian.terms {
let angle = t.clone() * (2.0 * coeff.re);
circuit.pauli_evolution(pauli, angle, qubits)?;
}
Ok(())
}
#[cfg(test)]
#[path = "hamiltonian_evolution_test.rs"]
mod hamiltonian_evolution_test;