Source code for tn4ml.initializers

from collections.abc import Callable
from typing import Any

import jax
import jax.numpy as jnp
import numpy as np
from jax import random
from jax._src import core, dtypes

from .util import gramschmidt_row


[docs] def zeros(std: Any = 1e-9, dtype: Any = jnp.float_) -> Callable: """Build an initializer that initializes tensors with zeros plus small noise. Examples -------- >>> import jax, jax.numpy as jnp >>> from tn4ml.initializers import zeros_init >>> initializer = zeros_init() >>> initializer(jax.random.key(42), (2, 2), jnp.float32) Array([[0., 0.], [0., 0.]], dtype=float32) """ def init(key: Any, shape: core.Shape, dtype: Any = dtype) -> jnp.ndarray: """Initialize a tensor. Parameters ---------- key : Any Random key. shape : core.Shape Shape of the tensor. dtype : Any Data type of the tensor. Returns ------- jnp.ndarray Initialized tensor. """ return jax.nn.initializers.zeros(key, shape, dtype) + std * random.normal( key, shape, dtype ) return init
[docs] def ones(std: Any = 1e-9, dtype: Any = jnp.float_) -> Callable: """Build an initializer that initializes tensors with ones plus small noise. Examples -------- >>> import jax, jax.numpy as jnp >>> from tn4ml.initializers import ones_init >>> initializer = ones_init() >>> initializer(jax.random.key(42), (2, 2), jnp.float32) Array([[1., 1.], [1., 1.]], dtype=float32) """ def init(key: Any, shape: core.Shape, dtype: Any = dtype) -> jnp.ndarray: """Initialize a tensor. Parameters ---------- key : Any Random key. shape : core.Shape Shape of the tensor. dtype : Any Data type of the tensor. Returns ------- jnp.ndarray Initialized tensor. """ return jax.nn.initializers.ones(key, shape, dtype) + std * random.normal( key, shape, dtype ) return init
[docs] def gramschmidt(dist: str, scale: Any = 1e-2, dtype: Any = jnp.float_) -> Callable: """Build an initializer using Gram-Schmidt orthogonalization. First, arrays are sampled from uniform or normal distribution (specified by `dist` argument). Parameters ---------- dist : str Sampling distribution of arrays. Options: `uniform`, `normal`. scale : Any (Optional). Default = `1e-2`. Scaling factor for the sampled arrays. dtype : Any (Optional) The initializer's default dtype. Examples -------- >>> import jax, jax.numpy as jnp >>> from tn4ml.initializers import gramschmidt_init >>> initializer = gramschmidt_init('normal') >>> initializer(jax.random.key(42), (2, 3), jnp.float32) Array([[ 0.35777482, 0.65598017, 0.6645954 ], [-0.57674366, -0.40450865, 0.70974606]], dtype=float32) """ def init(key: Any, shape: core.Shape, dtype: Any = dtype) -> jnp.ndarray: """Initialize a tensor. Parameters ---------- key : Any Random key. shape : core.Shape Shape of the tensor. dtype : Any Data type of the tensor. Returns ------- jnp.ndarray Initialized tensor. """ dtype = dtypes.canonicalize_dtype(dtype) matrix_shape: tuple = (shape[0], int(np.prod(shape[1:]))) if dist == "uniform": arrays = random.uniform(key, matrix_shape, dtype) * jnp.array(scale, dtype) elif dist == "normal": arrays = random.normal(key, matrix_shape, dtype) * jnp.array(scale, dtype) else: raise ValueError( "Sampling only implemented for 'uniform' and 'normal' distributions!" ) arrays = gramschmidt_row(arrays) return arrays.reshape(shape) return init
[docs] def identity(type: str, std: Any = None, dtype: Any = jnp.float_) -> Callable: # noqa: A002 """Build an initializer that sets identity values on diagonal or bond dimensions. Parameters ---------- type : str. Options: 'copy', 'bond' 'copy' = diagonal elements, 'bond' = bond dimension elements std : Any (Optional) Additonal noise dtype : Any (Optional). Default = `jnp.float_`. The initializer's default dtype. Examples -------- >>> import jax, jax.numpy as jnp >>> from tn4ml.initializers import gramschmidt_init >>> initializer = identity_init('copy', 1e-2) >>> initializer(jax.random.key(42), (3, 2), jnp.float32) Array([[ 1.0061227 , 0.01122588], [ 0.01137332, 0.99187267], [-0.00890405, 0.00126231]], dtype=float32) """ def init(key: Any, shape: core.Shape, dtype: Any = dtype) -> jnp.ndarray: """Initialize a tensor. Parameters ---------- key : Any Random key. shape : core.Shape Shape of the tensor. dtype : Any Data type of the tensor. Returns ------- jnp.ndarray Initialized tensor. """ dtype = dtypes.canonicalize_dtype(dtype) rank = len(shape) if type == "bond": tensor = jnp.zeros(shape, dtype=dtype) if rank == 4: eye_tensor = jnp.eye(shape[0], shape[1]).reshape( shape[0], shape[1], 1, 1 ) elif rank == 3: eye_tensor = jnp.eye(shape[0], shape[1]).reshape(shape[0], shape[1], 1) else: raise ValueError("Tensor should have LRP shape") # Use broadcasting to fill tensor slices tensor += eye_tensor elif type == "copy": # from @joserapa98/tensorkrowch tensor = jnp.zeros(shape, dtype=dtype) rank = len(shape) if rank <= 1: i: Any = 0 else: i = np.arange(min(shape), dtype=int) tensor = tensor.at[(i,) * rank].set(1.0) else: raise ValueError( "Defined only for diagonal and bond dimension identity intialization!" ) # Add random noise if std: tensor += std * random.normal(key, shape, dtype) return tensor return init
[docs] def randn( std: Any = 1.0, mean: Any = 0.0, noise_std: Any = None, noise_mean: Any = None, dtype: Any = jnp.float_, ) -> Callable: """Build an initializer that samples tensor values from a normal distribution. Parameters ---------- std : Any (Optional). Default = `1.0`. Standard deviation of the normal distribution mean : Any (Optional). Default = `0.0`. Mean of the normal distribution. noise_std : Any (Optional). Default = `None`. The standard deviation of the noise distribution (normal). noise_mean : Any (Optional). Default = `None`. The mean of the noise distribution (normal). dtype : Any (Optional). Default = `jnp.float_`. The initializer's default dtype. Examples -------- >>> import jax, jax.numpy as jnp >>> from tn4ml.initializers import randn_init >>> initializer = randn(1e-2) >>> initializer(jax.random.key(42), (2, 2), jnp.float32) Array([[ 0.00186935, 0.01065333], [-0.01559313, -0.01535296]], dtype=float32) """ def init(key: Any, shape: core.Shape, dtype: Any = dtype) -> jnp.ndarray: """Initialize a tensor. Parameters ---------- key : Any Random key. shape : core.Shape Shape of the tensor. dtype : Any Data type of the tensor. Returns ------- jnp.ndarray Initialized tensor. """ dtype = dtypes.canonicalize_dtype(dtype) tensor = random.normal(key, shape, dtype) mean_value = 0.0 if mean is None else mean tensor = mean_value + tensor * std if noise_std and noise_mean: noise = random.normal(key, shape, dtype) tensor += noise_mean + noise * noise_std return tensor return init
[docs] def unitary_matrix(key: Any, shape: core.Shape, dtype: Any = jnp.float_) -> jnp.ndarray: """ - from @joserapa98/tensorkrowch. Generates random unitary matrix from the Haar measure of size n x n. Unitary matrix is created as described in this `paper <https://arxiv.org/abs/math-ph/0609050v2>`_. Parameters ---------- key : Any Random key. shape : core.Shape Shape of the tensor. dtype : Any Data type of the tensor. Returns ------- jnp.ndarray Random unitary matrix. """ assert shape[0] == shape[1], "Matrix should be square!" mat = jax.random.normal(key, shape, dtype) q, r = jnp.linalg.qr(mat) d = jnp.diagonal(r) ph = d / jnp.abs(d) return q @ jnp.diag(ph)
[docs] def rand_unitary(dtype: Any = jnp.float_) -> Callable: """Build an initializer that stacks random unitary matrices. Parameters ---------- dtype : Any (Optional). Default = `jnp.float_`. The initializer's default dtype. Examples -------- >>> import jax, jax.numpy as jnp >>> from tn4ml.initializers import rand_unitary >>> initializer = rand_unitary() >>> initializer(jax.random.key(42), (2, 2), jnp.float32) Array([[ 0.11903083, 0.99289054], [-0.99289054, 0.11903088]], dtype=float32) >>> tensor = initializer(jax.random.key(42), (2, 2), jnp.float32) >>> jnp.allclose(tensor @ tensor.T.conj(), jnp.eye(2), atol=1e-6) True """ def init(key: Any, shape: core.Shape, dtype: Any = dtype) -> jnp.ndarray: """Initialize a tensor. Parameters ---------- key : Any Random key. shape : core.Shape Shape of the tensor. dtype : Any Data type of the tensor. Returns ------- jnp.ndarray Initialized tensor. """ dtype = dtypes.canonicalize_dtype(dtype) size = max(shape[0], shape[1], shape[2]) size_1 = min(shape[0], size) size_2 = min(shape[1], size) if len(shape) == 3: units = [] for _ in range(shape[2]): tensor = unitary_matrix(key, (size, size), dtype) tensor = tensor[:size_1, :size_2] units.append(tensor) tensor = jnp.stack(units, axis=-1) elif len(shape) == 4: units = [] for _ in range(shape[-2]): inner_units = [] for _ in range(shape[-1]): unitary = unitary_matrix(key, (size, size), dtype) unitary = unitary[:size_1, :size_2] inner_units.append(unitary) inner_stack = jnp.stack(inner_units, axis=-1) units.append(inner_stack) tensor = jnp.stack(units, axis=-1) else: raise ValueError("Only 3 and 4 rank tensors are supported!") return tensor return init