use crate::error::{QuantumError, QuantumResult};
use crate::handle::LcgRng;
use crate::pauli::hamiltonian::Hamiltonian;
use crate::vqe::ansatz::HardwareEfficientAnsatz;
use crate::vqe::vqe::VqeOptimizer;
#[derive(Debug, Clone)]
pub struct LayerwiseConfig {
pub n_qubits: usize,
pub target_depth: usize,
pub iters_per_stage: usize,
pub lr: f32,
pub seed_noise: f32,
}
impl LayerwiseConfig {
pub fn new(
n_qubits: usize,
target_depth: usize,
iters_per_stage: usize,
lr: f32,
seed_noise: f32,
) -> QuantumResult<Self> {
if n_qubits == 0 {
return Err(QuantumError::InvalidQubitCount { n: n_qubits });
}
if iters_per_stage == 0 {
return Err(QuantumError::InvalidParameter {
name: "iters_per_stage".into(),
});
}
if !lr.is_finite() || lr <= 0.0 {
return Err(QuantumError::InvalidParameter { name: "lr".into() });
}
Ok(Self {
n_qubits,
target_depth,
iters_per_stage,
lr,
seed_noise,
})
}
}
#[derive(Debug, Clone)]
pub struct LayerwiseResult {
pub params: Vec<f32>,
pub energy: f32,
pub stage_energies: Vec<f32>,
}
#[derive(Debug, Clone)]
pub struct LayerwiseVqe {
cfg: LayerwiseConfig,
ham: Hamiltonian,
}
impl LayerwiseVqe {
#[must_use]
pub fn new(cfg: LayerwiseConfig, ham: Hamiltonian) -> Self {
Self { cfg, ham }
}
pub fn train(&self, rng: &mut LcgRng) -> QuantumResult<LayerwiseResult> {
let mut stage_energies = Vec::with_capacity(self.cfg.target_depth + 1);
let ansatz0 = HardwareEfficientAnsatz::new(self.cfg.n_qubits, 0);
let mut params: Vec<f32> = (0..ansatz0.n_params())
.map(|_| rng.next_normal() * self.cfg.seed_noise)
.collect();
let mut opt = VqeOptimizer {
ansatz: ansatz0,
ham: self.ham.clone(),
params: params.clone(),
};
let (e0, p0) = opt.optimize(self.cfg.iters_per_stage, self.cfg.lr)?;
params = p0;
stage_energies.push(e0);
for depth in 1..=self.cfg.target_depth {
let ansatz = HardwareEfficientAnsatz::new(self.cfg.n_qubits, depth);
let mut grown = params.clone();
grown.extend(std::iter::repeat_n(0.0_f32, self.cfg.n_qubits));
debug_assert_eq!(grown.len(), ansatz.n_params());
let mut stage_opt = VqeOptimizer {
ansatz,
ham: self.ham.clone(),
params: grown,
};
let (e_stage, p_stage) = stage_opt.optimize(self.cfg.iters_per_stage, self.cfg.lr)?;
params = p_stage;
stage_energies.push(e_stage);
}
let final_energy = *stage_energies.last().unwrap_or(&f32::NAN);
Ok(LayerwiseResult {
params,
energy: final_energy,
stage_energies,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pauli::pauli_string::PauliOp;
#[test]
fn config_validation() {
assert!(LayerwiseConfig::new(0, 2, 5, 0.1, 0.05).is_err());
assert!(LayerwiseConfig::new(2, 2, 0, 0.1, 0.05).is_err());
assert!(LayerwiseConfig::new(2, 2, 5, 0.0, 0.05).is_err());
assert!(LayerwiseConfig::new(2, 2, 5, 0.1, 0.05).is_ok());
}
#[test]
fn identity_block_warm_start_does_not_increase_energy_at_grow_boundary() {
let cfg = LayerwiseConfig::new(2, 3, 8, 0.15, 0.05).expect("valid cfg");
let mut ham = Hamiltonian::new();
ham.add_term(1.0, vec![PauliOp::Z, PauliOp::Z]);
ham.add_term(-0.5, vec![PauliOp::X, PauliOp::I]);
let trainer = LayerwiseVqe::new(cfg, ham);
let mut rng = LcgRng::new(2024);
let res = trainer.train(&mut rng).expect("training succeeds");
assert_eq!(res.stage_energies.len(), 4); for w in res.stage_energies.windows(2) {
assert!(
w[1] <= w[0] + 5e-2,
"stage energy increased: {} → {}",
w[0],
w[1]
);
}
assert!(res.energy.is_finite());
}
#[test]
fn final_params_have_target_depth_length() {
let cfg = LayerwiseConfig::new(3, 2, 3, 0.1, 0.05).expect("valid cfg");
let mut ham = Hamiltonian::new();
ham.add_term(1.0, vec![PauliOp::Z, PauliOp::I, PauliOp::Z]);
let trainer = LayerwiseVqe::new(cfg, ham);
let mut rng = LcgRng::new(11);
let res = trainer.train(&mut rng).expect("train");
let expected = HardwareEfficientAnsatz::new(3, 2).n_params();
assert_eq!(res.params.len(), expected);
}
#[test]
fn ground_state_of_single_z_is_recovered() {
let cfg = LayerwiseConfig::new(1, 2, 40, 0.3, 0.1).expect("valid cfg");
let mut ham = Hamiltonian::new();
ham.add_term(1.0, vec![PauliOp::Z]);
let trainer = LayerwiseVqe::new(cfg, ham);
let mut rng = LcgRng::new(5);
let res = trainer.train(&mut rng).expect("train");
assert!(
res.energy < -0.9,
"energy={} should approach -1",
res.energy
);
}
}