Skip to content

NormalMPO

MPO subclass that tracks the overall scale factor separately from the unit-normed internal tensors.

NormalMPO

NormalMPO(
    tensors: List[Tensor],
    log_scale: float = 0.0,
    bc: str = "OBC",
    center: Optional[int] = None,
)

Bases: MPO

MPO with a separately tracked overall scale magnitude.

Represents the physical operator as exp(log_scale) × mpo, where the internal MPO satisfies mpo.norm() ≈ 1 after compact() or from_mpo(). Tracking the magnitude in log form (rather than as a raw float) keeps it representable even when it is far outside float64 range — this matters for XTRG, where Tr[ρ] is repeatedly squared and can reach ~10^500 or beyond at low temperature.

The sign of the physical operator lives directly in the internal MPO's tensor data. This works because mpo.norm() ≈ 1 does not pin down a sign (-X and X have the same Frobenius norm), and because canonicalization (compact(), canonical()) is an exact gauge transform that cannot change the value of any fully-contracted quantity such as Tr[ρ] — so whatever sign is baked into the tensors survives compaction unchanged. Whenever an operation needs to apply a sign flip (e.g. __mul__ by a negative scalar, or the alternating (-β)^n terms in thermal_mpo's Taylor accumulation), it is folded into site 0's tensor by multiplying it by -1.0. Site 0 is used by convention because, in an OBC chain, its left bond is the trivial boundary dimension, making it generically the cheapest tensor to touch. Because tensor contraction and addition are linear, sign flips baked into the operands' tensors propagate correctly through __matmul__/__add__ — see those methods' docstrings.

All arithmetic operations (+, @, *) preserve this representation, updating log_scale analytically (by addition, never by exponentiating a possibly-astronomical magnitude) without altering the unit-norm convention until compact() is called explicitly.

Parameters:

Name Type Description Default
tensors List[Tensor]

Non-empty list of site tensors, each with 4 axes (left_bond, right_bond, phys_in, phys_out).

required
log_scale float

log(scale), where scale = ||exp(log_scale) * mpo||_F is the (always non-negative) Frobenius-norm magnitude of the physical operator. Defaults to 0.0 (i.e. scale = 1.0).

0.0
bc str

Boundary condition: 'OBC' (default) or 'PBC'.

'OBC'
center Optional[int]

Orthogonality center site index, or None if unspecified.

None

Attributes

log_scale property

log_scale: float

log(scale) (read-only). The primary, overflow-safe representation.

scale property

scale: float

Overall scale magnitude as a raw, non-negative float (read-only).

Reconstructs exp(log_scale). For magnitudes too large to represent in float64, math.exp raises OverflowError; this is caught here and inf is returned instead, since a getter should never crash. Callers needing to represent genuinely astronomical magnitudes should use log_scale directly instead of this property.

Methods:

from_mpo classmethod

from_mpo(mpo: MPO) -> NormalMPO

Create a NormalMPO from a plain MPO.

The input MPO is not modified. The returned object has mpo.norm() ≈ 1 and scale == original_frobenius_norm.

Parameters:

Name Type Description Default
mpo MPO

Source MPO. May have any canonical form.

required

Returns:

Type Description
NormalMPO

Normalized copy with center=0.

Raises:

Type Description
ValueError

If the source MPO has exactly zero or non-finite norm.

scale_by

scale_by(log_scale_delta: float) -> None

Multiply the tracked scale magnitude in-place by exp(log_scale_delta).

This is the safe, in-place counterpart to __init__: it combines two log-magnitudes by addition rather than ever exponentiating either one, so it cannot overflow even when the combined scale would be far outside float64 range. Used by XTRG's _fit_mpo to fold pre-computed physical scale magnitudes into an already-compacted NormalMPO.

Parameters:

Name Type Description Default
log_scale_delta float

log(factor) of the multiplicative magnitude to apply.

required

compact

compact(trunc: Optional[dict] = None) -> None

Compress bond dimensions in-place, keeping the scale consistent.

Performs the same two-sweep canonicalization as MPO.compact(), but folds the extracted norm into log_scale instead of redistributing it across the site tensors. After the call, the internal MPO is approximately unit-normed and log_scale absorbs the physical magnitude.

Bond arrows may be reoriented by the QR/SVD sweeps; this method restores the standard IN/OUT convention via capcup after the sweeps, so that subsequent trace(), __add__, and __matmul__ calls work correctly.

Parameters:

Name Type Description Default
trunc Optional[dict]

Truncation options forwarded to canonical() during the right-to-left compression sweep. Defaults to {'thresh': 1e-14}.

None

log_trace

log_trace() -> Tuple[float, float]

Compute \(\log|\operatorname{Tr}[\rho]|\) and its sign.

Performs a left-to-right transfer-matrix sweep. At each site the physical indices (phys_in and phys_out, sharing itag s{i:02d} with opposite directions) are traced using Nicole's trace function, yielding a 2nd-order bond tensor. Adjacent bond tensors are chained with einsum.

This is the overflow-safe counterpart to trace(): it never forms Tr[ρ] itself, so it remains finite even when the physical trace would be far outside float64 range (as happens for XTRG runs at low temperature, where Tr[ρ] can reach ~10^500).

Returns:

Type Description
float

log|Tr[ρ]|, or -inf if the trace is exactly zero (sentinel, see _decompose_scale).

float

sign(Tr[ρ]): +1.0, -1.0, or 0.0 if the trace is exactly zero. Computed directly from the contracted tensor data (see the class docstring for where sign lives).

trace

trace() -> float

Compute the MPO trace \(\operatorname{Tr}[\rho]\).

Thin wrapper over log_trace() that exponentiates back to a raw magnitude. For magnitudes too large to represent in float64, ±inf is returned instead of raising (see scale). Prefer log_trace() directly when the trace magnitude may be extreme.

Returns:

Type Description
float

Tr[ρ], or ±inf if it overflows float64.

Notes

Representation. A NormalMPO stores the physical operator as exp(log_scale) × mpo, where after compact() or from_mpo() the internal MPO satisfies norm() == 1. Tracking the magnitude in log form as log_scale keeps it representable even far outside float64 range — this matters for XTRG, where Tr[ρ] is repeatedly squared and can reach ~10^500 or beyond at low temperature. The sign of the physical operator lives directly in the internal tensor data: mpo.norm() == 1 never pins down a sign (-X and X have the same Frobenius norm), so a sign flip (e.g. __mul__ by a negative scalar, or the alternating (-β)^n terms in thermal_mpo's Taylor accumulation) is folded into site 0's tensor by negating it — site 0 is used by convention since, in an OBC chain, its trivial boundary bond makes it the cheapest tensor to touch. Canonicalization (compact(), canonical()) is an exact gauge transform, so a sign baked into the tensors survives it unchanged.

Arithmetic. The operators @, +, *, and * (right) build new raw tensors and update log_scale analytically (by addition, never by exponentiating a possibly-astronomical magnitude) without performing any canonicalization. Since tensor contraction and addition are linear, sign carried by the operands' tensor data propagates through @ and + automatically. Call compact() explicitly afterward to restore the unit-norm invariant and truncate bond dimension. scale_by() provides the same safe, log-domain update for mutating an existing NormalMPO's scale magnitude in place.

Overflow-safe trace. trace() returns the raw magnitude Tr[ρ] and is convenient for moderate magnitudes, but may return ±inf once the true value exceeds float64 range. log_trace() returns (log|Tr[ρ]|, sign) instead, and never overflows in this regime — prefer it whenever the trace magnitude may be extreme (as in XTRG's cooling loop). The sign in this pair is derived by contracting the tensor data.

Dtype preservation. All arithmetic keeps real-valued (float64) tensors real. In __add__, the per-site scale factor is α^{1/L} where α = other.scale / self.scale is a non-negative magnitude ratio, so no complex roots arise.

See Also

  • MPO — base class.
  • thermal_mpo — constructs exp(-βH) as a NormalMPO.
  • observe — accepts a NormalMPO as state; computes Tr[ρ O] / Tr[ρ].