dseuss/mpnum

View on GitHub
mpnum/_testing.py

Summary

Maintainability
A
1 hr
Test Coverage
# encoding: utf-8
"""Auxiliary functions useful for writing tests"""


import itertools as it

import numpy as np
from numpy.testing import (assert_array_almost_equal, assert_array_equal,
                           assert_equal)
from scipy import sparse


def assert_mpa_almost_equal(mpa1, mpa2, full=False, **kwargs):
    """Verify that two MPAs are almost equal
    """
    if full:
        assert_array_almost_equal(mpa1.to_array(), mpa2.to_array(), **kwargs)
    else:
        raise NotImplementedError("Coming soon...")


def assert_mpa_identical(mpa1, mpa2, decimal=np.infty):
    """Verify that two MPAs are complety identical
    """
    assert len(mpa1) == len(mpa2)
    assert mpa1.canonical_form == mpa2.canonical_form
    assert mpa1.dtype == mpa2.dtype

    for i, lten1, lten2 in zip(it.count(), mpa1.lt, mpa2.lt):
        if decimal is np.infty:
            assert_array_equal(lten1, lten2,
                               err_msg='mismatch in lten {}'.format(i))
        else:
            assert_array_almost_equal(lten1, lten2, decimal=decimal,
                                      err_msg='mismatch in lten {}'.format(i))
    # TODO: We should make a comprehensive comparison between `mpa1`
    # and `mpa2`.  Are we missing other things?


def _assert_lcanonical(ltens, msg=''):
    ltens = ltens.reshape((np.prod(ltens.shape[:-1]), ltens.shape[-1]))
    prod = ltens.conj().T.dot(ltens)
    assert_array_almost_equal(prod, np.identity(prod.shape[0]),
                              err_msg=msg)


def _assert_rcanonical(ltens, msg=''):
    ltens = ltens.reshape((ltens.shape[0], np.prod(ltens.shape[1:])))
    prod = ltens.dot(ltens.conj().T)
    assert_array_almost_equal(prod, np.identity(prod.shape[0]),
                              err_msg=msg)


def assert_correct_normalization(lt, lcanon_target=None, rcanon_target=None):
    """Verify that normalization info in `lt` is correct

    We check that `lt` is at least as normalized as specified by the
    information. `lt` being "more normalized" than the information
    specifies is admissible and not treated as an error.

    If `[lr]canon_target` are not None, verify that normalization
    info is exactly equal to the given values.

    """
    if hasattr(lt, 'lt'):
        lt = lt.lt  # We got an MPArray in lt, retrieve mpa.lt
    lnormal, rnormal = lt.canonical_form

    # Verify that normalization info is correct
    for n in range(lnormal):
        _assert_lcanonical(lt[n], msg="Wrong left canonical (n={}/{})"
                           .format(n, lnormal))
    for n in range(rnormal, len(lt)):
        _assert_rcanonical(lt[n], msg="Wrong right canonical (n={}/{})"
                           .format(n, rnormal))

    # If targets are given, verify that the information in
    # `lt.canonical_form` matches the targets.
    if lcanon_target is not None:
        assert_equal(lnormal, lcanon_target)
    if rcanon_target is not None:
        assert_equal(rnormal, rcanon_target)


def compression_svd(array, rank, direction='right', retproj=False):
    """Re-implement MPArray.compress('svd') but on the level of the dense
    array representation, i.e. it truncates the Schmidt-decompostion
    on each bipartition sequentially.

    :param mpa: Array to compress
    :param rank: Compress to this rank
    :param direction: 'right' means sweep from left to right, 'left' vice versa
    :param retproj: Besides the compressed array, also return the projectors
        on the appropriate eigenspaces
    :returns: Result as numpy.ndarray

    """
    def singlecut(array, nr_left, target_rank):
        array_shape = array.shape
        array = array.reshape((np.prod(array_shape[:nr_left]), -1))
        u, s, vt = np.linalg.svd(array, full_matrices=False)
        u = u[:, :target_rank]
        s = s[:target_rank]
        vt = vt[:target_rank, :]
        opt_compr = np.dot(u * s, vt)
        opt_compr = opt_compr.reshape(array_shape)

        if retproj:
            projector_l = np.dot(u, u.T.conj())
            projector_r = np.dot(vt.T.conj(), vt)
            return opt_compr, (projector_l, projector_r)
        else:
            return opt_compr, (None, None)

    nr_sites = array.ndim
    projectors = []
    if direction == 'right':
        nr_left_values = range(1, nr_sites)
    else:
        nr_left_values = range(nr_sites-1, 0, -1)

    for nr_left in nr_left_values:
        array, proj = singlecut(array, nr_left, rank)
        projectors.append(proj)

    if direction != 'right':
        projectors = projectors.reverse()

    return (array, projectors) if retproj else array



def random_lowrank(rows, cols, rank, randstate=np.random, dtype=np.float_):
    """Returns a random lowrank matrix of given shape and dtype"""
    if dtype == np.float_:
        A = randstate.randn(rows, rank)
        B = randstate.randn(cols, rank)
    elif dtype == np.complex_:
        A = randstate.randn(rows, rank) + 1.j * randstate.randn(rows, rank)
        B = randstate.randn(cols, rank) + 1.j * randstate.randn(cols, rank)
    else:
        raise ValueError("{} is not a valid dtype".format(dtype))

    C = A.dot(B.conj().T)
    return C / np.linalg.norm(C)


def random_fullrank(rows, cols, **kwargs):
    """Returns a random matrix of given shape and dtype. Should provide
    same interface as random_lowrank"""
    kwargs.pop('rank', None)
    return random_lowrank(rows, cols, min(rows, cols), **kwargs)