use super::{MpsLimits, limit, tensor::*};
use crate::{ComplexTensor, Error, Result};
use burn::tensor::{DType, Device};
use num_complex::Complex64;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TruncationReport {
pub discarded_weight: f64,
pub trace_distance_bound: f64,
pub truncations: usize,
pub max_norm_drift: f64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SiteData {
pub shape: [usize; 3],
pub values: Vec<Complex64>,
}
impl SiteData {
pub(crate) fn validate(&self, limits: MpsLimits) -> Result<()> {
let [l, d, r] = self.shape;
if l == 0 || d < 2 || r == 0 {
return Err(Error::Shape("invalid MPS site shape".into()));
}
limit("MPS bond", l.max(r), limits.max_bond)?;
let n = crate::error::checked_product(self.shape)?;
limits.tensor(n)?;
if self.values.len() != n
|| self
.values
.iter()
.any(|z| !z.re.is_finite() || !z.im.is_finite())
{
return Err(Error::InvalidValue("invalid MPS site values".into()));
}
Ok(())
}
pub(crate) fn tensor(
&self,
device: &Device,
dtype: DType,
limits: MpsLimits,
) -> Result<ComplexTensor<3>> {
self.validate(limits)?;
from(&self.values, self.shape, device, dtype)
}
pub(crate) fn read(site: &ComplexTensor<3>) -> Result<Self> {
Ok(Self {
shape: site.dims(),
values: data(site)?,
})
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MpsCheckpoint {
pub version: u32,
pub sites: Vec<SiteData>,
pub center: usize,
pub report: TruncationReport,
}
#[derive(Clone, Debug)]
pub struct MpsState {
pub(super) sites: Vec<ComplexTensor<3>>,
pub(super) center: usize,
pub(super) limits: MpsLimits,
pub(super) report: TruncationReport,
}
impl MpsState {
pub fn basis(
dimensions: &[usize],
digits: &[usize],
device: &Device,
dtype: DType,
limits: MpsLimits,
) -> Result<Self> {
limits.validate()?;
crate::backend::supported_dtype(dtype)?;
limit("MPS sites", dimensions.len(), limits.max_sites)?;
if dimensions.is_empty()
|| dimensions.len() != digits.len()
|| dimensions.iter().any(|d| *d < 2)
{
return Err(Error::InvalidDimensions(
"MPS needs matching nonempty dimensions and digits".into(),
));
}
let count = dimensions
.iter()
.try_fold(0usize, |a, d| a.checked_add(*d).ok_or(Error::Overflow))?;
limit("MPS persistent state", count, limits.max_state_elements)?;
let mut sites = Vec::with_capacity(dimensions.len());
for (&d, &digit) in dimensions.iter().zip(digits) {
limits.tensor(d)?;
if digit >= d {
return Err(Error::Basis);
}
let mut values = vec![Complex64::new(0., 0.); d];
values[digit] = 1.0.into();
sites.push(from(&values, [1, d, 1], device, dtype)?);
}
Ok(Self {
sites,
center: 0,
limits,
report: TruncationReport::default(),
})
}
pub fn dimensions(&self) -> Vec<usize> {
self.sites.iter().map(|s| s.dims()[1]).collect()
}
pub fn storage_elements(&self) -> usize {
self.sites
.iter()
.map(|s| s.dims().iter().product::<usize>())
.sum()
}
pub fn bonds(&self) -> Vec<usize> {
self.sites
.iter()
.take(self.sites.len() - 1)
.map(|s| s.dims()[2])
.collect()
}
pub fn report(&self) -> TruncationReport {
self.report
}
pub fn device(&self) -> Device {
self.sites[0].device()
}
pub fn checkpoint(&self) -> Result<MpsCheckpoint> {
Ok(MpsCheckpoint {
version: 1,
sites: self
.sites
.iter()
.map(SiteData::read)
.collect::<Result<_>>()?,
center: self.center,
report: self.report,
})
}
pub fn restore(
checkpoint: &MpsCheckpoint,
device: &Device,
dtype: DType,
limits: MpsLimits,
) -> Result<Self> {
limits.validate()?;
crate::backend::supported_dtype(dtype)?;
limit("MPS sites", checkpoint.sites.len(), limits.max_sites)?;
if checkpoint.version != 1
|| checkpoint.sites.is_empty()
|| checkpoint.center >= checkpoint.sites.len()
{
return Err(Error::InvalidValue(
"invalid MPS checkpoint version/center".into(),
));
}
validate_report(checkpoint.report, limits)?;
let mut count = 0usize;
for (i, s) in checkpoint.sites.iter().enumerate() {
s.validate(limits)?;
count = count.checked_add(s.values.len()).ok_or(Error::Overflow)?;
limit("MPS persistent state", count, limits.max_state_elements)?;
if (i == 0 && s.shape[0] != 1)
|| (i + 1 == checkpoint.sites.len() && s.shape[2] != 1)
|| (i > 0 && checkpoint.sites[i - 1].shape[2] != s.shape[0])
{
return Err(Error::Shape("MPS checkpoint bond mismatch".into()));
}
validate_gauge(s, i.cmp(&checkpoint.center), 2e-5)?;
}
let state = Self {
sites: checkpoint
.sites
.iter()
.map(|s| s.tensor(device, dtype, limits))
.collect::<Result<_>>()?,
center: checkpoint.center,
limits,
report: checkpoint.report,
};
validate_norm(state.expectation_product(&[])?)?;
Ok(state)
}
}
pub(super) fn accumulate(
mut report: TruncationReport,
lost: f64,
limits: MpsLimits,
) -> Result<TruncationReport> {
report.discarded_weight += lost;
report.trace_distance_bound = (report.trace_distance_bound + lost.sqrt()).min(1.);
if lost > 0. {
report.truncations = report.truncations.checked_add(1).ok_or(Error::Overflow)?;
}
if report.trace_distance_bound > limits.max_truncation_distance + 1e-12 {
return Err(Error::InvalidValue(format!(
"MPS truncation distance {} exceeds budget {}",
report.trace_distance_bound, limits.max_truncation_distance
)));
}
Ok(report)
}
pub(super) fn validate_report(r: TruncationReport, limits: MpsLimits) -> Result<()> {
if !r.discarded_weight.is_finite()
|| r.discarded_weight < 0.
|| !r.trace_distance_bound.is_finite()
|| !(0.0..=1.).contains(&r.trace_distance_bound)
|| !r.max_norm_drift.is_finite()
|| r.max_norm_drift < 0.
|| r.trace_distance_bound > limits.max_truncation_distance + 1e-12
{
return Err(Error::InvalidValue("invalid MPS error report".into()));
}
Ok(())
}
pub(crate) fn validate_gauge(
site: &SiteData,
position: std::cmp::Ordering,
tolerance: f64,
) -> Result<()> {
let [l, d, r] = site.shape;
let v = &site.values;
match position {
std::cmp::Ordering::Equal => {
let norm: f64 = v.iter().map(|z| z.norm_sqr()).sum();
if (norm - 1.).abs() > tolerance {
return Err(Error::InvalidValue("MPS center is not normalized".into()));
}
}
std::cmp::Ordering::Less => {
for a in 0..r {
for b in 0..r {
let dot: Complex64 =
(0..l * d).map(|x| v[x * r + a].conj() * v[x * r + b]).sum();
if (dot
- if a == b {
Complex64::new(1., 0.)
} else {
Complex64::new(0., 0.)
})
.norm()
> tolerance
{
return Err(Error::InvalidValue(
"MPS left gauge is not isometric".into(),
));
}
}
}
}
std::cmp::Ordering::Greater => {
for a in 0..l {
for b in 0..l {
let dot: Complex64 = (0..d * r)
.map(|x| v[a * d * r + x] * v[b * d * r + x].conj())
.sum();
if (dot
- if a == b {
Complex64::new(1., 0.)
} else {
Complex64::new(0., 0.)
})
.norm()
> tolerance
{
return Err(Error::InvalidValue(
"MPS right gauge is not isometric".into(),
));
}
}
}
}
}
Ok(())
}
pub(crate) fn validate_norm(norm: Complex64) -> Result<()> {
if !norm.re.is_finite() || !norm.im.is_finite() || (norm - Complex64::new(1., 0.)).norm() > 2e-5
{
return Err(Error::InvalidValue(
"MPS global norm differs from one".into(),
));
}
Ok(())
}