use crate::error::OptimizeError;
use crate::quantum_classical::QcResult;
#[derive(Debug, Clone)]
pub struct Tensor {
pub data: Vec<f64>,
pub shape: Vec<usize>,
}
impl Tensor {
pub fn zeros(shape: &[usize]) -> Self {
let size = shape.iter().product();
Self {
data: vec![0.0; size],
shape: shape.to_vec(),
}
}
pub fn size(&self) -> usize {
self.data.len()
}
pub fn ndim(&self) -> usize {
self.shape.len()
}
#[inline]
pub fn get(&self, idx: &[usize]) -> f64 {
let flat = self.flat_index(idx);
self.data[flat]
}
#[inline]
pub fn set(&mut self, idx: &[usize], val: f64) {
let flat = self.flat_index(idx);
self.data[flat] = val;
}
fn flat_index(&self, idx: &[usize]) -> usize {
let mut flat = 0;
let mut stride = 1;
for (i, &dim) in self.shape.iter().enumerate().rev() {
flat += idx[i] * stride;
stride *= dim;
}
flat
}
}
#[derive(Debug, Clone)]
pub struct MPS {
pub tensors: Vec<Tensor>,
pub n_sites: usize,
pub phys_dim: usize,
pub max_bond_dim: usize,
}
impl MPS {
pub fn product_state(values: &[usize], phys_dim: usize) -> QcResult<Self> {
if values.is_empty() {
return Err(OptimizeError::ValueError(
"Product state must have at least one site".to_string(),
));
}
for &v in values {
if v >= phys_dim {
return Err(OptimizeError::ValueError(format!(
"State value {v} exceeds phys_dim={phys_dim}"
)));
}
}
let n = values.len();
let tensors: Vec<Tensor> = values
.iter()
.map(|&v| {
let mut t = Tensor::zeros(&[1, phys_dim, 1]);
t.set(&[0, v, 0], 1.0);
t
})
.collect();
Ok(Self {
tensors,
n_sites: n,
phys_dim,
max_bond_dim: 1,
})
}
pub fn bond_dim_left(&self, site: usize) -> usize {
self.tensors[site].shape[0]
}
pub fn bond_dim_right(&self, site: usize) -> usize {
self.tensors[site].shape[2]
}
pub fn overlap(&self, other: &MPS) -> QcResult<f64> {
if self.n_sites != other.n_sites {
return Err(OptimizeError::ValueError(
"MPS must have the same number of sites for overlap".to_string(),
));
}
if self.phys_dim != other.phys_dim {
return Err(OptimizeError::ValueError(
"MPS must have the same physical dimension for overlap".to_string(),
));
}
let n = self.n_sites;
let mut transfer: Vec<Vec<f64>> = vec![vec![1.0]];
for site in 0..n {
let ta = &self.tensors[site]; let tb = &other.tensors[site];
let dl_a = ta.shape[0];
let d = ta.shape[1];
let dr_a = ta.shape[2];
let dl_b = tb.shape[0];
let dr_b = tb.shape[2];
let mut new_transfer = vec![vec![0.0; dr_b]; dr_a];
for alpha_new in 0..dr_a {
for beta_new in 0..dr_b {
let mut val = 0.0;
for alpha in 0..dl_a {
for beta in 0..dl_b {
let t_ab = transfer[alpha][beta];
if t_ab.abs() < f64::EPSILON * 1e-6 {
continue;
}
for sigma in 0..d {
let a_val = ta.get(&[alpha, sigma, alpha_new]);
let b_val = tb.get(&[beta, sigma, beta_new]);
val += t_ab * a_val * b_val;
}
}
}
new_transfer[alpha_new][beta_new] = val;
}
}
transfer = new_transfer;
}
if transfer.len() != 1 || transfer[0].len() != 1 {
return Err(OptimizeError::ComputationError(
"Transfer matrix should be 1x1 at the end".to_string(),
));
}
Ok(transfer[0][0])
}
pub fn norm(&self) -> QcResult<f64> {
let norm2 = self.overlap(self)?;
Ok(norm2.abs().sqrt())
}
pub fn normalize(&mut self) -> QcResult<()> {
let n = self.norm()?;
let threshold = f64::EPSILON * 1e3;
if n < threshold {
let half = (0.5_f64).sqrt();
for t in &mut self.tensors {
let d = t.shape[1];
for val in &mut t.data {
*val = 0.0;
}
let dl = t.shape[0];
let dr = t.shape[2];
for al in 0..dl.min(1) {
for sig in 0..d {
for ar in 0..dr.min(1) {
let flat = (al * d + sig) * dr + ar;
if flat < t.data.len() {
t.data[flat] = if d <= 2 {
half
} else {
1.0 / (d as f64).sqrt()
};
}
}
}
}
}
return Ok(());
}
for val in &mut self.tensors[0].data {
*val /= n;
}
Ok(())
}
}
fn svd_2d(a: &[f64], m: usize, n: usize) -> QcResult<(Vec<f64>, Vec<f64>, Vec<f64>)> {
if a.len() != m * n {
return Err(OptimizeError::ValueError(format!(
"Matrix size mismatch: {} != {}*{}",
a.len(),
m,
n
)));
}
let k = m.min(n);
jacobi_svd(a, m, n, k)
}
fn jacobi_svd(a: &[f64], m: usize, n: usize, k: usize) -> QcResult<(Vec<f64>, Vec<f64>, Vec<f64>)> {
let mut v_data = vec![0.0_f64; n * k]; for i in 0..k {
v_data[i * k + i] = 1.0; }
let mut v_data2 = vec![0.0_f64; n * k];
for i in 0..k {
v_data2[i * k + i] = 1.0;
}
let _ = v_data;
let mut b = a.to_vec();
let mut v_full = vec![0.0_f64; n * n];
for i in 0..n {
v_full[i * n + i] = 1.0;
}
let max_sweeps = 100;
let tol = 1e-12_f64;
for _sweep in 0..max_sweeps {
let mut changed = false;
for p in 0..n {
for q in (p + 1)..n {
let bpp: f64 = (0..m).map(|i| b[i * n + p] * b[i * n + p]).sum();
let bqq: f64 = (0..m).map(|i| b[i * n + q] * b[i * n + q]).sum();
let bpq: f64 = (0..m).map(|i| b[i * n + p] * b[i * n + q]).sum();
if bpq.abs() < tol * (bpp * bqq).sqrt().max(1e-300) {
continue;
}
changed = true;
let tau = (bqq - bpp) / (2.0 * bpq);
let t = if tau >= 0.0 {
1.0 / (tau + (1.0 + tau * tau).sqrt())
} else {
1.0 / (tau - (1.0 + tau * tau).sqrt())
};
let c = 1.0 / (1.0 + t * t).sqrt();
let s = t * c;
for i in 0..m {
let bp = b[i * n + p];
let bq = b[i * n + q];
b[i * n + p] = c * bp - s * bq;
b[i * n + q] = s * bp + c * bq;
}
for i in 0..n {
let vp = v_full[i * n + p];
let vq = v_full[i * n + q];
v_full[i * n + p] = c * vp - s * vq;
v_full[i * n + q] = s * vp + c * vq;
}
}
}
if !changed {
break;
}
}
let mut sigma = vec![0.0_f64; k];
let mut u_data = vec![0.0_f64; m * k];
for j in 0..k {
let col_norm: f64 = (0..m)
.map(|i| b[i * n + j] * b[i * n + j])
.sum::<f64>()
.sqrt();
sigma[j] = col_norm;
if col_norm > tol {
for i in 0..m {
u_data[i * k + j] = b[i * n + j] / col_norm;
}
} else {
if j < m {
u_data[j * k + j] = 1.0;
}
}
}
let mut vt_data = vec![0.0_f64; k * n];
for j in 0..k {
for i in 0..n {
vt_data[j * n + i] = v_full[i * n + j];
}
}
let mut order: Vec<usize> = (0..k).collect();
order.sort_by(|&a, &b| {
sigma[b]
.partial_cmp(&sigma[a])
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut u_sorted = vec![0.0_f64; m * k];
let mut s_sorted = vec![0.0_f64; k];
let mut vt_sorted = vec![0.0_f64; k * n];
for (new_j, &old_j) in order.iter().enumerate() {
s_sorted[new_j] = sigma[old_j];
for i in 0..m {
u_sorted[i * k + new_j] = u_data[i * k + old_j];
}
for i in 0..n {
vt_sorted[new_j * n + i] = vt_data[old_j * n + i];
}
}
Ok((u_sorted, s_sorted, vt_sorted))
}
pub fn svd_compress_bond(
tensor: &Tensor,
max_bond_dim: usize,
) -> QcResult<(Tensor, Vec<f64>, Tensor)> {
if tensor.shape.len() != 2 {
return Err(OptimizeError::ValueError(
"svd_compress_bond expects a 2D tensor".to_string(),
));
}
let m = tensor.shape[0];
let n = tensor.shape[1];
let (u, s, vt) = svd_2d(&tensor.data, m, n)?;
let k_full = m.min(n);
let tol = s[0] * 1e-12_f64.max(f64::EPSILON);
let chi = (0..k_full)
.filter(|&i| i < max_bond_dim && s[i] > tol)
.count()
.max(1);
let chi = chi.min(max_bond_dim);
let mut u_tensor = Tensor::zeros(&[m, chi]);
for i in 0..m {
for j in 0..chi {
u_tensor.data[i * chi + j] = u[i * k_full + j];
}
}
let s_trunc: Vec<f64> = s[..chi].to_vec();
let mut vt_tensor = Tensor::zeros(&[chi, n]);
for i in 0..chi {
for j in 0..n {
vt_tensor.data[i * n + j] = vt[i * n + j];
}
}
Ok((u_tensor, s_trunc, vt_tensor))
}
pub fn mpo_apply(mps: &MPS, mpo_tensors: &[Tensor]) -> QcResult<MPS> {
if mps.n_sites != mpo_tensors.len() {
return Err(OptimizeError::ValueError(format!(
"MPS has {} sites but MPO has {} tensors",
mps.n_sites,
mpo_tensors.len()
)));
}
let n = mps.n_sites;
let d = mps.phys_dim;
let mut new_tensors: Vec<Tensor> = Vec::with_capacity(n);
for site in 0..n {
let a = &mps.tensors[site]; let w = &mpo_tensors[site];
let dl_mps = a.shape[0];
let dr_mps = a.shape[2];
let dl_mpo = w.shape[0];
let dr_mpo = w.shape[3];
let d_out = w.shape[1];
if w.shape[2] != d {
return Err(OptimizeError::ValueError(format!(
"MPO physical in-dim {} != MPS phys_dim {}",
w.shape[2], d
)));
}
let dl_new = dl_mps * dl_mpo;
let dr_new = dr_mps * dr_mpo;
let mut new_t = Tensor::zeros(&[dl_new, d_out, dr_new]);
for alpha_mps in 0..dl_mps {
for alpha_mpo in 0..dl_mpo {
for sigma_out in 0..d_out {
for beta_mps in 0..dr_mps {
for beta_mpo in 0..dr_mpo {
let mut val = 0.0;
for sigma_in in 0..d {
let a_val = a.get(&[alpha_mps, sigma_in, beta_mps]);
let w_val = w.get(&[alpha_mpo, sigma_out, sigma_in, beta_mpo]);
val += a_val * w_val;
}
let alpha_new = alpha_mps * dl_mpo + alpha_mpo;
let beta_new = beta_mps * dr_mpo + beta_mpo;
let cur = new_t.get(&[alpha_new, sigma_out, beta_new]);
new_t.set(&[alpha_new, sigma_out, beta_new], cur + val);
}
}
}
}
}
new_tensors.push(new_t);
}
let max_bond = mps.max_bond_dim * mpo_tensors[0].shape[0]; let mut result = MPS {
tensors: new_tensors,
n_sites: n,
phys_dim: d_out_from_mpo(mpo_tensors),
max_bond_dim: max_bond,
};
compress_mps_left_to_right(&mut result, mps.max_bond_dim)?;
Ok(result)
}
fn d_out_from_mpo(mpo: &[Tensor]) -> usize {
if mpo.is_empty() {
2
} else {
mpo[0].shape[1]
}
}
fn compress_mps_left_to_right(mps: &mut MPS, max_bond: usize) -> QcResult<()> {
let n = mps.n_sites;
for site in 0..(n - 1) {
let t = &mps.tensors[site];
let dl = t.shape[0];
let d = t.shape[1];
let dr = t.shape[2];
let mut mat = Tensor::zeros(&[dl * d, dr]);
for al in 0..dl {
for sig in 0..d {
for ar in 0..dr {
let v = t.get(&[al, sig, ar]);
mat.data[(al * d + sig) * dr + ar] = v;
}
}
}
let (u, s, vt) = svd_compress_bond(&mat, max_bond)?;
let chi = s.len();
let mut new_left = Tensor::zeros(&[dl, d, chi]);
for al in 0..dl {
for sig in 0..d {
for j in 0..chi {
let v = u.data[(al * d + sig) * chi + j];
new_left.set(&[al, sig, j], v);
}
}
}
mps.tensors[site] = new_left;
let right = &mps.tensors[site + 1].clone();
let dr_right = right.shape[2];
let d_right = right.shape[1];
let dl_right = right.shape[0];
let mut new_right = Tensor::zeros(&[chi, d_right, dr_right]);
for j in 0..chi {
for k in 0..dl_right.min(vt.shape[1]) {
let sv_jk = s[j] * vt.data[j * vt.shape[1] + k];
if sv_jk.abs() < f64::EPSILON * 1e-10 {
continue;
}
for sig in 0..d_right {
for beta in 0..dr_right {
let r = right.get(&[k, sig, beta]);
let cur = new_right.get(&[j, sig, beta]);
new_right.set(&[j, sig, beta], cur + sv_jk * r);
}
}
}
}
mps.tensors[site + 1] = new_right;
}
mps.max_bond_dim = max_bond;
Ok(())
}
pub fn ising_1d_mpo(n_sites: usize, j: f64, h: f64) -> Vec<Tensor> {
if n_sites == 0 {
return Vec::new();
}
let id: [f64; 4] = [1.0, 0.0, 0.0, 1.0]; let z_mat: [f64; 4] = [1.0, 0.0, 0.0, -1.0]; let x_mat: [f64; 4] = [0.0, 1.0, 1.0, 0.0]; let zero: [f64; 4] = [0.0; 4];
let d_mpo = 3usize;
let d = 2usize;
let make_bulk_tensor = |left_boundary: bool, right_boundary: bool| -> Tensor {
let dl = if left_boundary { 1 } else { d_mpo };
let dr = if right_boundary { 1 } else { d_mpo };
let mut t = Tensor::zeros(&[dl, d, d, dr]);
let a_start = if left_boundary { 0 } else { 0 };
let b_end = if right_boundary { 0 } else { 2 };
let _ = a_start;
let _ = b_end;
for sigma_out in 0..d {
for sigma_in in 0..d {
let ops: &[([f64; 4], usize, usize)] = &[
(id, 0, 0),
(z_mat, 0, 1),
(x_mat_scaled(-h), 0, 2),
(z_mat_scaled(-j), 1, 2),
(id, 2, 2),
];
for &(ref op, a, b) in ops {
let a_idx = if left_boundary {
if a == 0 {
Some(0)
} else {
None
}
} else {
Some(a)
};
let b_idx = if right_boundary {
if b == d_mpo - 1 {
Some(0)
} else {
None
}
} else {
Some(b)
};
if let (Some(ai), Some(bi)) = (a_idx, b_idx) {
let cur = t.get(&[ai, sigma_out, sigma_in, bi]);
t.set(
&[ai, sigma_out, sigma_in, bi],
cur + op[sigma_out * 2 + sigma_in],
);
}
}
}
}
t
};
let mut mpo = Vec::with_capacity(n_sites);
for site in 0..n_sites {
let left_boundary = site == 0;
let right_boundary = site == n_sites - 1;
mpo.push(make_bulk_tensor(left_boundary, right_boundary));
}
mpo
}
fn z_mat_scaled(scale: f64) -> [f64; 4] {
[scale, 0.0, 0.0, -scale]
}
fn x_mat_scaled(scale: f64) -> [f64; 4] {
[0.0, scale, scale, 0.0]
}
fn mps_energy(mps: &MPS, hamiltonian_mpo: &[Tensor]) -> QcResult<f64> {
mps_expectation_value(mps, hamiltonian_mpo)
}
fn mps_expectation_value(mps: &MPS, mpo: &[Tensor]) -> QcResult<f64> {
let n = mps.n_sites;
if n != mpo.len() {
return Err(OptimizeError::ValueError(
"MPS and MPO sizes must match".to_string(),
));
}
let dl_bra_0 = mps.tensors[0].shape[0]; let dl_mpo_0 = mpo[0].shape[0]; let dl_ket_0 = mps.tensors[0].shape[0];
let mut transfer = vec![0.0f64; dl_bra_0 * dl_mpo_0 * dl_ket_0];
if dl_bra_0 == 1 && dl_mpo_0 == 1 && dl_ket_0 == 1 {
transfer[0] = 1.0;
} else {
return Err(OptimizeError::ComputationError(
"Left boundary bond dims must be 1".to_string(),
));
}
let mut d_bra = dl_bra_0;
let mut d_mpo_cur = dl_mpo_0;
let mut d_ket = dl_ket_0;
for site in 0..n {
let a = &mps.tensors[site]; let w = &mpo[site];
let dr_bra = a.shape[2];
let dr_mpo = w.shape[3];
let dr_ket = a.shape[2];
let phys_d = a.shape[1];
let mut new_transfer = vec![0.0f64; dr_bra * dr_mpo * dr_ket];
for alpha_bra in 0..d_bra {
for a_mpo in 0..d_mpo_cur {
for alpha_ket in 0..d_ket {
let t_val =
transfer[alpha_bra * (d_mpo_cur * d_ket) + a_mpo * d_ket + alpha_ket];
if t_val.abs() < f64::EPSILON * 1e-12 {
continue;
}
for sigma in 0..phys_d {
for sigma_p in 0..phys_d {
for b_mpo in 0..dr_mpo {
let w_val = w.get(&[a_mpo, sigma, sigma_p, b_mpo]);
if w_val.abs() < f64::EPSILON * 1e-12 {
continue;
}
for beta_bra in 0..dr_bra {
let a_bra_val = a.get(&[alpha_bra, sigma, beta_bra]);
for beta_ket in 0..dr_ket {
let a_ket_val = a.get(&[alpha_ket, sigma_p, beta_ket]);
let idx = beta_bra * (dr_mpo * dr_ket)
+ b_mpo * dr_ket
+ beta_ket;
new_transfer[idx] += t_val * a_bra_val * w_val * a_ket_val;
}
}
}
}
}
}
}
}
transfer = new_transfer;
d_bra = dr_bra;
d_mpo_cur = dr_mpo;
d_ket = dr_ket;
}
if transfer.len() == 1 {
let numerator = transfer[0];
let norm2 = mps.overlap(mps)?;
if norm2.abs() < f64::EPSILON * 1e-6 {
return Err(OptimizeError::ComputationError(
"MPS norm is zero in energy computation".to_string(),
));
}
Ok(numerator / norm2)
} else {
let numerator: f64 = transfer.iter().sum();
let norm2 = mps.overlap(mps)?;
if norm2.abs() < f64::EPSILON * 1e-6 {
return Err(OptimizeError::ComputationError(
"MPS norm is zero in energy computation".to_string(),
));
}
Ok(numerator / norm2)
}
}
#[derive(Debug, Clone)]
struct EnvBlock {
data: Vec<f64>,
d_bra: usize,
d_mpo: usize,
d_ket: usize,
}
impl EnvBlock {
fn zeros(d_bra: usize, d_mpo: usize, d_ket: usize) -> Self {
Self {
data: vec![0.0; d_bra * d_mpo * d_ket],
d_bra,
d_mpo,
d_ket,
}
}
fn get(&self, alpha_bra: usize, a_mpo: usize, alpha_ket: usize) -> f64 {
self.data[alpha_bra * (self.d_mpo * self.d_ket) + a_mpo * self.d_ket + alpha_ket]
}
fn add(&mut self, alpha_bra: usize, a_mpo: usize, alpha_ket: usize, val: f64) {
let idx = alpha_bra * (self.d_mpo * self.d_ket) + a_mpo * self.d_ket + alpha_ket;
self.data[idx] += val;
}
}
fn build_left_envs(mps: &MPS, mpo: &[Tensor]) -> QcResult<Vec<EnvBlock>> {
let n = mps.n_sites;
let mut envs: Vec<EnvBlock> = Vec::with_capacity(n + 1);
let mut l0 = EnvBlock::zeros(1, 1, 1);
l0.data[0] = 1.0;
envs.push(l0);
for site in 0..n {
let a = &mps.tensors[site];
let w = &mpo[site];
let prev = &envs[site];
let dr_bra = a.shape[2];
let dr_mpo = w.shape[3];
let dr_ket = a.shape[2];
let phys_d = a.shape[1];
let dl_mpo = w.shape[0];
let d_bra_in = prev.d_bra;
let d_mpo_in = prev.d_mpo;
let d_ket_in = prev.d_ket;
let mut new_env = EnvBlock::zeros(dr_bra, dr_mpo, dr_ket);
for alpha_bra in 0..d_bra_in {
for a_idx in 0..d_mpo_in {
for alpha_ket in 0..d_ket_in {
let p = prev.get(alpha_bra, a_idx, alpha_ket);
if p.abs() < f64::EPSILON * 1e-12 {
continue;
}
for sigma in 0..phys_d {
for sigma_p in 0..phys_d {
for b_mpo in 0..dr_mpo {
if a_idx >= dl_mpo {
continue;
}
let w_val = w.get(&[a_idx, sigma, sigma_p, b_mpo]);
if w_val.abs() < f64::EPSILON * 1e-12 {
continue;
}
for beta_bra in 0..dr_bra {
let a_bra = a.get(&[alpha_bra, sigma, beta_bra]);
if a_bra.abs() < f64::EPSILON * 1e-12 {
continue;
}
for beta_ket in 0..dr_ket {
let a_ket = a.get(&[alpha_ket, sigma_p, beta_ket]);
new_env.add(
beta_bra,
b_mpo,
beta_ket,
p * a_bra * w_val * a_ket,
);
}
}
}
}
}
}
}
}
envs.push(new_env);
}
Ok(envs)
}
fn build_right_envs(mps: &MPS, mpo: &[Tensor]) -> QcResult<Vec<EnvBlock>> {
let n = mps.n_sites;
let mut envs: Vec<EnvBlock> = vec![EnvBlock::zeros(1, 1, 1); n + 1];
let mut r_last = EnvBlock::zeros(1, 1, 1);
r_last.data[0] = 1.0;
envs[n] = r_last;
for site in (0..n).rev() {
let a = &mps.tensors[site];
let w = &mpo[site];
let next = &envs[site + 1].clone();
let dl_bra = a.shape[0];
let dl_mpo = w.shape[0];
let dl_ket = a.shape[0];
let phys_d = a.shape[1];
let dr_mpo = w.shape[3];
let d_bra_in = next.d_bra;
let d_mpo_in = next.d_mpo;
let d_ket_in = next.d_ket;
let mut new_env = EnvBlock::zeros(dl_bra, dl_mpo, dl_ket);
for beta_bra in 0..d_bra_in {
for b_mpo in 0..d_mpo_in {
for beta_ket in 0..d_ket_in {
let p = next.get(beta_bra, b_mpo, beta_ket);
if p.abs() < f64::EPSILON * 1e-12 {
continue;
}
for sigma in 0..phys_d {
for sigma_p in 0..phys_d {
for a_mpo in 0..dl_mpo {
if b_mpo >= dr_mpo {
continue;
}
let w_val = w.get(&[a_mpo, sigma, sigma_p, b_mpo]);
if w_val.abs() < f64::EPSILON * 1e-12 {
continue;
}
for alpha_bra in 0..dl_bra {
let a_bra = a.get(&[alpha_bra, sigma, beta_bra]);
if a_bra.abs() < f64::EPSILON * 1e-12 {
continue;
}
for alpha_ket in 0..dl_ket {
let a_ket = a.get(&[alpha_ket, sigma_p, beta_ket]);
new_env.add(
alpha_bra,
a_mpo,
alpha_ket,
p * a_bra * w_val * a_ket,
);
}
}
}
}
}
}
}
}
envs[site] = new_env;
}
Ok(envs)
}
fn apply_heff_two_site(
theta: &Tensor,
left_env: &EnvBlock,
w1: &Tensor,
w2: &Tensor,
right_env: &EnvBlock,
) -> Tensor {
let dl = theta.shape[0];
let d = theta.shape[1];
let dr = theta.shape[3];
let d_left_bra = left_env.d_bra;
let d_left_mpo = left_env.d_mpo;
let d_left_ket = left_env.d_ket;
let d_right_bra = right_env.d_bra;
let d_right_mpo = right_env.d_mpo;
let d_right_ket = right_env.d_ket;
let dm_mpo = w1.shape[3];
let _ = d_left_bra;
let _ = d_right_bra;
let mut result = Tensor::zeros(&[dl, d, d, dr]);
for alpha_l_out in 0..dl {
for sig1_out in 0..d {
for sig2_out in 0..d {
for alpha_r_out in 0..dr {
let mut val = 0.0;
for alpha_l_in in 0..d_left_ket {
for sig1_in in 0..d {
for sig2_in in 0..d {
for alpha_r_in in 0..d_right_ket {
let theta_val =
theta.get(&[alpha_l_in, sig1_in, sig2_in, alpha_r_in]);
if theta_val.abs() < f64::EPSILON * 1e-12 {
continue;
}
for a_mpo in 0..d_left_mpo {
let l_val = left_env.get(alpha_l_out, a_mpo, alpha_l_in);
if l_val.abs() < f64::EPSILON * 1e-12 {
continue;
}
for b_mpo in 0..dm_mpo {
let w1_val = w1.get(&[a_mpo, sig1_out, sig1_in, b_mpo]);
if w1_val.abs() < f64::EPSILON * 1e-12 {
continue;
}
for c_mpo in 0..d_right_mpo {
let w2_val =
w2.get(&[b_mpo, sig2_out, sig2_in, c_mpo]);
if w2_val.abs() < f64::EPSILON * 1e-12 {
continue;
}
let r_val =
right_env.get(alpha_r_out, c_mpo, alpha_r_in);
val += l_val * w1_val * w2_val * r_val * theta_val;
}
}
}
}
}
}
}
let cur = result.get(&[alpha_l_out, sig1_out, sig2_out, alpha_r_out]);
result.set(&[alpha_l_out, sig1_out, sig2_out, alpha_r_out], cur + val);
}
}
}
}
result
}
pub fn dmrg_two_site_sweep(
mps: &mut MPS,
hamiltonian_mpo: &[Tensor],
n_sweeps: usize,
) -> QcResult<f64> {
if mps.n_sites < 2 {
return Err(OptimizeError::ValueError(
"DMRG requires at least 2 sites".to_string(),
));
}
if mps.n_sites != hamiltonian_mpo.len() {
return Err(OptimizeError::ValueError(
"MPS and MPO must have the same number of sites".to_string(),
));
}
mps.normalize()?;
let mut energy = mps_energy(mps, hamiltonian_mpo)?;
for _sweep in 0..n_sweeps {
let right_envs = build_right_envs(mps, hamiltonian_mpo)?;
let mut left_env = {
let mut l0 = EnvBlock::zeros(1, 1, 1);
l0.data[0] = 1.0;
l0
};
let n = mps.n_sites;
for site in 0..(n - 1) {
let right_env = &right_envs[site + 2];
let w1 = &hamiltonian_mpo[site];
let w2 = &hamiltonian_mpo[site + 1];
let (mut theta, dl, d, dr) = build_theta(mps, site)?;
let n_iter = 3;
for _pi in 0..n_iter {
let h_theta = apply_heff_two_site(&theta, &left_env, w1, w2, right_env);
let norm: f64 = h_theta.data.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm > f64::EPSILON {
let rayleigh = compute_rayleigh(&theta, &h_theta);
let alpha = 0.2 / (rayleigh.abs() + 1.0);
for i in 0..theta.data.len() {
theta.data[i] -= alpha * h_theta.data[i];
}
let n2: f64 = theta.data.iter().map(|x| x * x).sum::<f64>().sqrt();
if n2 > f64::EPSILON {
for v in &mut theta.data {
*v /= n2;
}
}
}
}
update_mps_from_theta(mps, &theta, site, dl, d, dr, true)?;
left_env = contract_left_env(&left_env, &mps.tensors[site], &hamiltonian_mpo[site])?;
}
mps.normalize()?;
let new_energy = mps_energy(mps, hamiltonian_mpo)?;
energy = new_energy;
}
Ok(energy)
}
fn compute_rayleigh(theta: &Tensor, h_theta: &Tensor) -> f64 {
let num: f64 = theta
.data
.iter()
.zip(h_theta.data.iter())
.map(|(a, b)| a * b)
.sum();
let denom: f64 = theta.data.iter().map(|x| x * x).sum();
if denom.abs() < f64::EPSILON {
0.0
} else {
num / denom
}
}
fn build_theta(mps: &MPS, site: usize) -> QcResult<(Tensor, usize, usize, usize)> {
let a1 = &mps.tensors[site];
let a2 = &mps.tensors[site + 1];
let dl = a1.shape[0];
let d = a1.shape[1];
let dm = a1.shape[2];
let dr = a2.shape[2];
let mut theta = Tensor::zeros(&[dl, d, d, dr]);
for al in 0..dl {
for s1 in 0..d {
for gam in 0..dm {
let v1 = a1.get(&[al, s1, gam]);
if v1.abs() < f64::EPSILON * 1e-12 {
continue;
}
for s2 in 0..d {
for ar in 0..dr {
let v2 = a2.get(&[gam, s2, ar]);
let cur = theta.get(&[al, s1, s2, ar]);
theta.set(&[al, s1, s2, ar], cur + v1 * v2);
}
}
}
}
}
Ok((theta, dl, d, dr))
}
fn update_mps_from_theta(
mps: &mut MPS,
theta: &Tensor,
site: usize,
dl: usize,
d: usize,
dr: usize,
_left_canonical: bool,
) -> QcResult<()> {
let rows = dl * d;
let cols = d * dr;
let mut mat = Tensor::zeros(&[rows, cols]);
for al in 0..dl {
for s1 in 0..d {
for s2 in 0..d {
for ar in 0..dr {
let v = theta.get(&[al, s1, s2, ar]);
mat.data[(al * d + s1) * cols + s2 * dr + ar] = v;
}
}
}
}
let max_bond = mps.max_bond_dim;
let (u_mat, s_vals, vt_mat) = svd_compress_bond(&mat, max_bond)?;
let chi = s_vals.len();
let mut new_a1 = Tensor::zeros(&[dl, d, chi]);
for al in 0..dl {
for s1 in 0..d {
for j in 0..chi {
let v = u_mat.data[(al * d + s1) * chi + j];
new_a1.set(&[al, s1, j], v);
}
}
}
let mut new_a2 = Tensor::zeros(&[chi, d, dr]);
for j in 0..chi {
for s2 in 0..d {
for ar in 0..dr {
let v = s_vals[j] * vt_mat.data[j * cols + s2 * dr + ar];
new_a2.set(&[j, s2, ar], v);
}
}
}
mps.tensors[site] = new_a1;
mps.tensors[site + 1] = new_a2;
Ok(())
}
fn contract_left_env(left: &EnvBlock, a: &Tensor, w: &Tensor) -> QcResult<EnvBlock> {
let dr_bra = a.shape[2];
let dr_mpo = w.shape[3];
let dr_ket = a.shape[2];
let phys_d = a.shape[1];
let dl_mpo = w.shape[0];
let mut new_env = EnvBlock::zeros(dr_bra, dr_mpo, dr_ket);
for alpha_bra in 0..left.d_bra {
for a_idx in 0..left.d_mpo {
for alpha_ket in 0..left.d_ket {
let p = left.get(alpha_bra, a_idx, alpha_ket);
if p.abs() < f64::EPSILON * 1e-12 {
continue;
}
for sigma in 0..phys_d {
for sigma_p in 0..phys_d {
for b_mpo in 0..dr_mpo {
if a_idx >= dl_mpo {
continue;
}
let w_val = w.get(&[a_idx, sigma, sigma_p, b_mpo]);
if w_val.abs() < f64::EPSILON * 1e-12 {
continue;
}
for beta_bra in 0..dr_bra {
let a_bra = a.get(&[alpha_bra, sigma, beta_bra]);
if a_bra.abs() < f64::EPSILON * 1e-12 {
continue;
}
for beta_ket in 0..dr_ket {
let a_ket = a.get(&[alpha_ket, sigma_p, beta_ket]);
new_env.add(
beta_bra,
b_mpo,
beta_ket,
p * a_bra * w_val * a_ket,
);
}
}
}
}
}
}
}
}
Ok(new_env)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_product_state_bond_dim_one() {
let mps = MPS::product_state(&[0, 1, 0, 1], 2).unwrap();
assert_eq!(mps.n_sites, 4);
for t in &mps.tensors {
assert_eq!(t.shape[0], 1, "left bond dim should be 1");
assert_eq!(t.shape[2], 1, "right bond dim should be 1");
}
}
#[test]
fn test_overlap_identical_states() {
let mps = MPS::product_state(&[0, 1, 0], 2).unwrap();
let norm2 = mps.overlap(&mps).unwrap();
let norm = mps.norm().unwrap();
assert!((norm2 - norm * norm).abs() < 1e-12, "overlap == norm²");
}
#[test]
fn test_product_state_normalized() {
let mps = MPS::product_state(&[1, 0, 1, 0], 2).unwrap();
let n = mps.norm().unwrap();
assert!(
(n - 1.0).abs() < 1e-12,
"Product state should be normalized"
);
}
#[test]
fn test_svd_compress_reduces_bond_dim() {
let mut mat = Tensor::zeros(&[4, 4]);
for i in 0..4 {
for j in 0..4 {
mat.data[i * 4 + j] = (i + j + 1) as f64;
}
}
let (u, s, vt) = svd_compress_bond(&mat, 2).unwrap();
assert!(s.len() <= 2, "Should have ≤ 2 singular values");
assert_eq!(u.shape[1], s.len());
assert_eq!(vt.shape[0], s.len());
}
#[test]
fn test_ising_mpo_structure() {
let n = 4;
let mpo = ising_1d_mpo(n, 1.0, 0.5);
assert_eq!(mpo.len(), n);
for t in &mpo {
assert_eq!(t.shape[1], 2, "MPO phys out dim should be 2");
assert_eq!(t.shape[2], 2, "MPO phys in dim should be 2");
}
}
#[test]
fn test_mpo_apply_valid_mps() {
let n = 3;
let mps = MPS::product_state(&[0, 1, 0], 2).unwrap();
let mpo = ising_1d_mpo(n, 1.0, 0.5);
let result = mpo_apply(&mps, &mpo).unwrap();
assert_eq!(result.n_sites, n);
assert_eq!(result.phys_dim, 2);
}
#[test]
fn test_dmrg_ising_ground_state_energy() {
let n = 4;
let j = 1.0_f64;
let h = 0.3_f64;
let mut mps = MPS::product_state(&[0, 0, 0, 0], 2).unwrap();
mps.max_bond_dim = 4;
let ham_mpo = ising_1d_mpo(n, j, h);
let energy = dmrg_two_site_sweep(&mut mps, &ham_mpo, 10).unwrap();
assert!(
energy < -1.0,
"DMRG energy {energy:.4} should be < -1.0 (ferromagnetic Ising ground state region)"
);
}
}