use ariadnetor_core::Scalar;
use ariadnetor_linalg::{LinalgError, contract};
use ariadnetor_mps::{Mpo, Mps, TensorChain};
use ariadnetor_tensor::{
DenseLayout, DenseStorage, Host, Storage, StorageFor, Tensor, TensorLayout,
};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum DmrgEnvError {
#[error("MPS / MPO has zero sites")]
EmptyChain,
#[error("MPS and MPO site counts differ: mps = {mps}, mpo = {mpo}")]
LengthMismatch {
mps: usize,
mpo: usize,
},
#[error("site index {index} out of range for chain of length {n_sites}")]
InvalidSite {
index: usize,
n_sites: usize,
},
#[error(
"advance prerequisite {side} env at index {index} is stale (None); \
build the initial envs or advance in order"
)]
StaleNeighbor {
side: &'static str,
index: usize,
},
#[error("contract failure during DMRG environment update")]
Contract(#[from] LinalgError),
#[error("malformed edge bond on {leg}: {detail}", detail = edge_bond_detail(.leg))]
MalformedEdgeBond {
leg: &'static str,
},
}
fn edge_bond_detail(leg: &str) -> &'static str {
match leg {
"mps_left" | "mps_right" => "must be dim-1 / single-sector",
"mpo_left" | "mpo_right" => {
"must be dim-1 / single-sector with sector fusing to identity flux"
}
_ => "must be dim-1 / single-sector",
}
}
pub trait DmrgEnvOps<T: Scalar>: super::sealed::Sealed {
type Layout: TensorLayout;
type Storage: Storage + StorageFor<Self::Layout>;
fn trivial_left_boundary(
mps_left_edge: &Tensor<Self::Storage, Self::Layout>,
mpo_left_edge: &Tensor<Self::Storage, Self::Layout>,
) -> Result<Tensor<Self::Storage, Self::Layout>, DmrgEnvError>;
fn trivial_right_boundary(
mps_right_edge: &Tensor<Self::Storage, Self::Layout>,
mpo_right_edge: &Tensor<Self::Storage, Self::Layout>,
) -> Result<Tensor<Self::Storage, Self::Layout>, DmrgEnvError>;
fn extend_left_step(
env: &Tensor<Self::Storage, Self::Layout>,
site: &Tensor<Self::Storage, Self::Layout>,
mpo_site: &Tensor<Self::Storage, Self::Layout>,
) -> Result<Tensor<Self::Storage, Self::Layout>, LinalgError>;
fn extend_right_step(
env: &Tensor<Self::Storage, Self::Layout>,
site: &Tensor<Self::Storage, Self::Layout>,
mpo_site: &Tensor<Self::Storage, Self::Layout>,
) -> Result<Tensor<Self::Storage, Self::Layout>, LinalgError>;
}
impl<T: Scalar> DmrgEnvOps<T> for DmrgEnvs<DenseStorage<T>, DenseLayout> {
type Layout = DenseLayout;
type Storage = DenseStorage<T>;
fn trivial_left_boundary(
_mps_left_edge: &Tensor<Self::Storage, Self::Layout>,
_mpo_left_edge: &Tensor<Self::Storage, Self::Layout>,
) -> Result<Tensor<Self::Storage, Self::Layout>, DmrgEnvError> {
Ok(make_dense_one())
}
fn trivial_right_boundary(
_mps_right_edge: &Tensor<Self::Storage, Self::Layout>,
_mpo_right_edge: &Tensor<Self::Storage, Self::Layout>,
) -> Result<Tensor<Self::Storage, Self::Layout>, DmrgEnvError> {
Ok(make_dense_one())
}
fn extend_left_step(
env: &Tensor<Self::Storage, Self::Layout>,
site: &Tensor<Self::Storage, Self::Layout>,
mpo_site: &Tensor<Self::Storage, Self::Layout>,
) -> Result<Tensor<Self::Storage, Self::Layout>, LinalgError> {
let backend = Host::shared();
let bra = site.conj();
let t1 = contract(backend.as_ref(), env, &bra, "abc,ade->bcde")?;
let t2 = contract(backend.as_ref(), &t1, mpo_site, "bcde,bfdg->cefg")?;
contract(backend.as_ref(), &t2, site, "cefg,cfh->egh")
}
fn extend_right_step(
env: &Tensor<Self::Storage, Self::Layout>,
site: &Tensor<Self::Storage, Self::Layout>,
mpo_site: &Tensor<Self::Storage, Self::Layout>,
) -> Result<Tensor<Self::Storage, Self::Layout>, LinalgError> {
let backend = Host::shared();
let bra = site.conj();
let t1 = contract(backend.as_ref(), env, site, "egh,cfh->egcf")?;
let t2 = contract(backend.as_ref(), &t1, mpo_site, "egcf,bfdg->ecbd")?;
contract(backend.as_ref(), &t2, &bra, "ecbd,ade->abc")
}
}
fn make_dense_one<T>() -> Tensor<DenseStorage<T>, DenseLayout>
where
T: Scalar,
{
ariadnetor_tensor::DenseTensor::<T>::ones(vec![1, 1, 1])
}
#[derive(Debug, Clone)]
pub struct DmrgEnvs<St, L>
where
St: Storage + StorageFor<L>,
L: TensorLayout,
{
left: Vec<Option<Tensor<St, L>>>,
right: Vec<Option<Tensor<St, L>>>,
n_sites: usize,
}
impl<St, L> DmrgEnvs<St, L>
where
St: Storage + StorageFor<L>,
L: TensorLayout,
{
pub fn build<T>(mps: &Mps<St, L>, mpo: &Mpo<St, L>) -> Result<Self, DmrgEnvError>
where
T: Scalar,
Self: DmrgEnvOps<T, Storage = St, Layout = L>,
{
let n_sites = mps.len();
if n_sites == 0 {
return Err(DmrgEnvError::EmptyChain);
}
if mpo.len() != n_sites {
return Err(DmrgEnvError::LengthMismatch {
mps: n_sites,
mpo: mpo.len(),
});
}
let mut left: Vec<Option<Tensor<St, L>>> = (0..=n_sites).map(|_| None).collect();
let mut right: Vec<Option<Tensor<St, L>>> = (0..=n_sites).map(|_| None).collect();
left[0] = Some(<Self as DmrgEnvOps<T>>::trivial_left_boundary(
mps.site(0),
mpo.site(0),
)?);
right[n_sites] = Some(<Self as DmrgEnvOps<T>>::trivial_right_boundary(
mps.site(n_sites - 1),
mpo.site(n_sites - 1),
)?);
for j in (1..=n_sites).rev() {
if j == 1 {
break;
}
let prev = right[j].as_ref().expect("just initialized or computed");
let new =
<Self as DmrgEnvOps<T>>::extend_right_step(prev, mps.site(j - 1), mpo.site(j - 1))?;
right[j - 1] = Some(new);
}
Ok(Self {
left,
right,
n_sites,
})
}
pub fn n_sites(&self) -> usize {
self.n_sites
}
pub fn left(&self, i: usize) -> Option<&Tensor<St, L>> {
self.left.get(i).and_then(Option::as_ref)
}
pub fn right(&self, j: usize) -> Option<&Tensor<St, L>> {
self.right.get(j).and_then(Option::as_ref)
}
pub fn advance_left<T>(
&mut self,
mps: &Mps<St, L>,
mpo: &Mpo<St, L>,
i: usize,
) -> Result<(), DmrgEnvError>
where
T: Scalar,
Self: DmrgEnvOps<T, Storage = St, Layout = L>,
{
if i >= self.n_sites {
return Err(DmrgEnvError::InvalidSite {
index: i,
n_sites: self.n_sites,
});
}
if mpo.len() != self.n_sites || mps.len() != self.n_sites {
return Err(DmrgEnvError::LengthMismatch {
mps: mps.len(),
mpo: mpo.len(),
});
}
let prev = match &self.left[i] {
Some(t) => t,
None => {
return Err(DmrgEnvError::StaleNeighbor {
side: "left",
index: i,
});
}
};
let new = <Self as DmrgEnvOps<T>>::extend_left_step(prev, mps.site(i), mpo.site(i))?;
self.left[i + 1] = Some(new);
if i + 1 < self.n_sites {
self.right[i + 1] = None;
}
Ok(())
}
pub fn advance_right<T>(
&mut self,
mps: &Mps<St, L>,
mpo: &Mpo<St, L>,
j: usize,
) -> Result<(), DmrgEnvError>
where
T: Scalar,
Self: DmrgEnvOps<T, Storage = St, Layout = L>,
{
if j >= self.n_sites {
return Err(DmrgEnvError::InvalidSite {
index: j,
n_sites: self.n_sites,
});
}
if mpo.len() != self.n_sites || mps.len() != self.n_sites {
return Err(DmrgEnvError::LengthMismatch {
mps: mps.len(),
mpo: mpo.len(),
});
}
let prev = match &self.right[j + 1] {
Some(t) => t,
None => {
return Err(DmrgEnvError::StaleNeighbor {
side: "right",
index: j + 1,
});
}
};
let new = <Self as DmrgEnvOps<T>>::extend_right_step(prev, mps.site(j), mpo.site(j))?;
self.right[j] = Some(new);
if j > 0 {
self.left[j] = None;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn malformed_edge_bond_mpo_detail_is_distinct() {
let mpo = DmrgEnvError::MalformedEdgeBond { leg: "mpo_left" }.to_string();
assert!(mpo.contains("fusing to identity flux"));
let mps = DmrgEnvError::MalformedEdgeBond { leg: "mps_left" }.to_string();
assert!(!mps.contains("fusing to identity flux"));
}
}