{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Sparse Tensors\n", "```\n", "Copyright 2022 National Technology & Engineering Solutions of Sandia,\n", "LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the\n", "U.S. Government retains certain rights in this software.\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating a `sptensor`\n", "The `sptensor` class stores the data in coordinate format. A sparse `sptensor` can be created by passing in a list of subscripts and values. For example, here we pass in three subscripts and a scalar value. The resulting sparse `sptensor` has three nonzero entries, and the `shape` is the size of the largest subscript in each dimension." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pyttb as ttb\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0)\n", "subs = np.array([[0, 0, 0], [0, 1, 0], [2, 3, 1]]) # Subscripts of the nonzeros.\n", "vals = np.array([[1], [2], [3]]) # Vals is a column vector; values of the nonzeros.\n", "X = ttb.sptensor.from_aggregator(subs, vals) # Sparse tensor with 3 nonzeros." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor(subs, vals, (3, 5, 2)) # Or, specify the shape explicitly.\n", "X" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Values corresponding to repeated subscripts are summed." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "subs = np.array(\n", " [[0, 0, 0], [0, 0, 2], [2, 2, 2], [3, 3, 3], [0, 0, 0], [0, 0, 0]]\n", ") # (1,1,1) is repeated.\n", "vals = np.array([2, 2, 2, 2, 2, 2])[:, None] # Vals is a column vector.\n", "X = ttb.sptensor.from_aggregator(subs, vals)\n", "X" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Specifying the accumulation method for the constructor" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "subs = np.array([[0, 0, 0], [0, 0, 2], [2, 2, 2], [3, 3, 3], [0, 0, 0], [0, 0, 0]])\n", "vals = 2 * np.ones((6, 1)) # A column vector of 2s\n", "shape = (4, 4, 4)\n", "X = ttb.sptensor.from_aggregator(subs, vals, shape, np.max) # Maximum element.\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "myfun = myfun = lambda x: np.sum(x) / 3 # Total sum divided by three.\n", "X = ttb.sptensor.from_aggregator(\n", " subs, vals, shape, myfun\n", ") # Custom accumulation function.\n", "X" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating a one-dimensional `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor.from_aggregator(np.array([[0], [2], [4]]), np.ones((3, 1)))\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0)\n", "X = ttb.sptenrand((50,), nonzeros=5)\n", "X" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating an all-zero `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor()\n", "X[9, 9, 9] = 0 # Creates an all-zero tensor.\n", "X" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Constituent parts of a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0)\n", "X = ttb.sptenrand([40, 30, 20], nonzeros=5) # Create data.\n", "X.subs # Subscripts of nonzeros." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X.vals # Corresponding nonzero values." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X.shape # The shape." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating a `sptensor` from its constituent parts" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0)\n", "X = ttb.sptenrand([40, 30, 20], nonzeros=5) # Create data.\n", "Y = X.copy()\n", "Y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating an empty `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Y = ttb.sptensor() # Create an empty sptensor.\n", "Y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use `sptenrand` to create a random `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0)\n", "X = ttb.sptenrand(\n", " [10, 10, 10], 0.01\n", ") # Create a tesnor with 1% nonzeros using the 'density' param.\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0)\n", "X = ttb.sptenrand(\n", " [10, 10, 10], nonzeros=10\n", ") # Create a tensor with 10 nonzeros using the 'nonzeros' param.\n", "X" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use `squeeze` to remove singleton dimensions from a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "indices = np.array([[0, 0, 0], [1, 0, 0]])\n", "values = np.ones((2, 1))\n", "\n", "Y = ttb.sptensor.from_aggregator(indices, values) # Create a sparse tensor.\n", "Y" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Y.squeeze() # Remove singleton dimensions." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use `squash` to remove empty slices from a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "indices = np.array([[0, 0, 0], [2, 2, 2]])\n", "values = np.array([[1], [3]])\n", "\n", "Y = ttb.sptensor.from_aggregator(indices, values) # Create a sparse tensor.\n", "Y" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Y.squash()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use `full` or `to_tensor` to convert a `sptensor` to a (dense) `tensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "indices = np.array([[0, 0, 0], [1, 1, 1]])\n", "values = np.array([[1], [1]])\n", "X = ttb.sptensor.from_aggregator(indices, values) # Create a sparse tensor.\n", "X.full() # Convert it to a (dense) tensor." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Y = X.to_tensor() # Same as above.\n", "Y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use `to_sptensor` to convert a (dense) `tensor` to a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "indices = np.array([[0, 0, 0], [1, 1, 1]])\n", "values = np.array([[1], [1]])\n", "X = ttb.sptensor.from_aggregator(indices, values) # Create a sparse tensor.\n", "Y = X.to_tensor() # Convert it to a (dense) tensor.\n", "Z = Y.to_sptensor() # Convert a tensor to a sptensor.\n", "Z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use `double` to convert a `sptensor` to a (dense) multidimensional array" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "indices = np.array([[0, 0, 0], [1, 1, 1]])\n", "values = np.array([[1], [1]])\n", "X = ttb.sptensor.from_aggregator(indices, values) # Create a sparse tensor.\n", "Y = ttb.sptensor.double(X) # Creates numpy.ndarray\n", "Y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use `find` to extract nonzeros from a `tensor` and then create a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0)\n", "X = ttb.tensor(np.random.rand(5, 4, 2)) # Create a tensor.\n", "larger_entries = X > 0.9 # Extract subscipts of values greater than 0.9.\n", "subs, vals = larger_entries.find() # Extract corresponding subscripts and values.\n", "Y = ttb.sptensor.from_aggregator(subs, vals) # Create a new sptensor.\n", "Y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use `ndims` and `shape` to get the shape of a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor(\n", " np.array([[1, 1, 1], [2, 3, 2], [3, 4, 1], [1, 0, 0]]),\n", " np.array([[3], [2], [1], [3]]),\n", " (4, 5, 3),\n", ")\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X.ndims # Number of dimensions or modes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X.shape # Shape of X." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X.shape[2] # Shape of mode 3 of X." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use `nnz` to get the number of nonzeroes of a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor(\n", " np.array([[1, 1, 1], [2, 3, 2], [3, 4, 1], [1, 0, 0]]),\n", " np.array([[3], [2], [1], [3]]),\n", " (4, 5, 3),\n", ")\n", "X.nnz # Number of nonzeros of X." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Subscripted reference for a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor(\n", " np.array([[3, 3, 3], [1, 1, 0], [1, 2, 1]]), np.array([[3], [5], [1]]), (4, 4, 4)\n", ") # Create a sptensor.\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[0, 1, 0] # Extract the (0,1,0) element, which is zero." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[3, 3, 3] # Extract the (3,3,3) element, which is non-zero." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[0:2, 1:4, :] # Extract the 2x3x4 subtensor." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[1, 1, 1]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[1, 1, 0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[[0, 5]] # Same as above but with linear indices." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "indices = np.array([[0], [2], [4]])\n", "values = np.array([[1], [1], [1]])\n", "X = ttb.sptensor.from_aggregator(indices, values)\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[(2,)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[[2, 4],] # Returns a subtensor." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Subscripted assignment for a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor(np.array([[]]), np.array([[]]), (30, 40, 20))\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[29, 39, 19] = 7 # Assign a single element.\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[0, 0, 0], X[1, 1, 1] = [1, 1] # Assign a list of elements.\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0)\n", "Y = ttb.sptenrand((10, 10, 10), nonzeros=10)\n", "X[10:20, 10:20, 10:20] = Y # Assign a subtensor." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[30, 40, 20] = 4 # Grows the shape of the sptensor.\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[110:120, 110:120, 110:120] = ttb.sptenrand((10, 10, 10), nonzeros=10) # Grow more." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using negative indexing for the last array index" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X[-10:, -10:, -5:]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use `elemfun` to manipulate the nonzeros of a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0)\n", "X = ttb.sptenrand((10, 10, 10), nonzeros=3)\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Z = X.elemfun(lambda value: np.sqrt(value)) # Square root of every nonzero.\n", "Z" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Z = X.elemfun(lambda value: value + 1) # Use a custom function.\n", "Z" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Z = X.ones() # Change every nonzero to one.\n", "Z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basic operations (plus, minus, times, etc.) on a `sptensor`\n", "`sptensor`s support plus, minus, times, divide, power, equals, and not-equals operators. `sptensor`s can use their operators with another `sptensor` or a scalar (with the exception of equalities which only takes `sptensor`s). All mathematical operators are elementwise operations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Addition" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor(np.array([[0, 0], [1, 1]]), np.array([[2], [2]]), (2, 2))\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Y = ttb.sptensor(np.array([[0, 0], [0, 1]]), np.array([[3], [3]]), (2, 2))\n", "Y" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "+X # Calls uplus." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X + 1 # This addition yields dense tensor" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X + Y # This addition yields sparse tensor" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X += 2\n", "X" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Subtraction" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor(np.array([[0, 0], [1, 1]]), np.array([[2], [2]]), (2, 2))\n", "Y = ttb.sptensor(np.array([[0, 0], [0, 1]]), np.array([[3], [3]]), (2, 2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "-X # Calls uminus." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X - Y # Calls minus." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Multiplication" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor(np.array([[0, 0], [1, 1]]), np.array([[2], [2]]), (2, 2))\n", "Y = ttb.sptensor(np.array([[0, 0], [0, 1]]), np.array([[3], [3]]), (2, 2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X * Y # Calls times." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X * 5 # Calls mtimes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Division" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor(np.array([[0, 0], [1, 1]]), np.array([[2], [2]]), (2, 2))\n", "Y = ttb.sptensor(np.array([[0, 0], [0, 1]]), np.array([[3], [3]]), (2, 2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X / 2 # Calls rdivide." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X / Y # Divide by Zero" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X /= 4\n", "print(X)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Equality" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = ttb.sptensor(np.array([[0, 0], [1, 1]]), np.array([[2], [2]]), (2, 2))\n", "Y = ttb.sptensor(np.array([[0, 0], [0, 1]]), np.array([[3], [3]]), (2, 2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "(X == Y)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X.isequal(Y)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X != Y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use `permute` to reorder the modes of a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0)\n", "X = ttb.sptenrand((30, 40, 20, 1), nonzeros=5) # Create data.\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X.permute(np.array([3, 2, 1, 0])) # Reorder the modes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`permute` works correctly for a 1-dimensional `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0)\n", "X = ttb.sptenrand((40,), nonzeros=4) # Create data.\n", "X" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X.permute(np.array([0]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Displaying a `sptensor`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(X)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X # In the python interface" ] } ], "metadata": {}, "nbformat": 4, "nbformat_minor": 1 }