Code Monkey home page Code Monkey logo

tensor-puzzles's Introduction

Tensor Puzzles

When learning a tensor programming language like PyTorch or Numpy it is tempting to rely on the standard library (or more honestly StackOverflow) to find a magic function for everything. But in practice, the tensor language is extremely expressive, and you can do most things from first principles and clever use of broadcasting.

This is a collection of 21 tensor puzzles. Like chess puzzles these are not meant to simulate the complexity of a real program, but to practice in a simplified environment. Each puzzle asks you to reimplement one function in the NumPy standard library without magic.

I recommend running in Colab. Click here and copy the notebook to get start.

Open In Colab

If you are interested, there is also a youtube walkthrough of the puzzles

Watch the video

!pip install -qqq torchtyping hypothesis pytest git+https://github.com/danoneata/chalk@srush-patch-1
!wget -q https://github.com/srush/Tensor-Puzzles/raw/main/lib.py
from lib import draw_examples, make_test, run_test
import torch
import numpy as np
from torchtyping import TensorType as TT
tensor = torch.tensor

Rules

  1. These puzzles are about broadcasting. Know this rule.

  1. Each puzzle needs to be solved in 1 line (<80 columns) of code.

  2. You are allowed @, arithmetic, comparison, shape, any indexing (e.g. a[:j], a[:, None], a[arange(10)]), and previous puzzle functions.

  3. You are not allowed anything else. No view, sum, take, squeeze, tensor.

  4. You can start with these two functions:

def arange(i: int):
    "Use this function to replace a for-loop."
    return torch.tensor(range(i))

draw_examples("arange", [{"" : arange(i)} for i in [5, 3, 9]])

svg

# Example of broadcasting.
examples = [(arange(4), arange(5)[:, None]) ,
            (arange(3)[:, None], arange(2))]
draw_examples("broadcast", [{"a": a, "b":b, "ret": a + b} for a, b in examples])

svg

def where(q, a, b):
    "Use this function to replace an if-statement."
    return (q * a) + (~q) * b

# In diagrams, orange is positive/True, where is zero/False, and blue is negative.

examples = [(tensor([False]), tensor([10]), tensor([0])),
            (tensor([False, True]), tensor([1, 1]), tensor([-10, 0])),
            (tensor([False, True]), tensor([1]), tensor([-10, 0])),
            (tensor([[False, True], [True, False]]), tensor([1]), tensor([-10, 0])),
            (tensor([[False, True], [True, False]]), tensor([[0], [10]]), tensor([-10, 0])),
           ]
draw_examples("where", [{"q": q, "a":a, "b":b, "ret": where(q, a, b)} for q, a, b in examples])

svg

Puzzle 1 - ones

Compute ones - the vector of all ones.

def ones_spec(out):
    for i in range(len(out)):
        out[i] = 1
        
def ones(i: int) -> TT["i"]:
    raise NotImplementedError

test_ones = make_test("one", ones, ones_spec, add_sizes=["i"])

svg

# run_test(test_ones)

Puzzle 2 - sum

Compute sum - the sum of a vector.

def sum_spec(a, out):
    out[0] = 0
    for i in range(len(a)):
        out[0] += a[i]
        
def sum(a: TT["i"]) -> TT[1]:
    raise NotImplementedError


test_sum = make_test("sum", sum, sum_spec)

svg

# run_test(test_sum)

Puzzle 3 - outer

Compute outer - the outer product of two vectors.

def outer_spec(a, b, out):
    for i in range(len(out)):
        for j in range(len(out[0])):
            out[i][j] = a[i] * b[j]
            
def outer(a: TT["i"], b: TT["j"]) -> TT["i", "j"]:
    raise NotImplementedError
    
test_outer = make_test("outer", outer, outer_spec)

svg

# run_test(test_outer)

Puzzle 4 - diag

Compute diag - the diagonal vector of a square matrix.

def diag_spec(a, out):
    for i in range(len(a)):
        out[i] = a[i][i]
        
def diag(a: TT["i", "i"]) -> TT["i"]:
    raise NotImplementedError


test_diag = make_test("diag", diag, diag_spec)

svg

# run_test(test_diag)

Puzzle 5 - eye

Compute eye - the identity matrix.

def eye_spec(out):
    for i in range(len(out)):
        out[i][i] = 1
        
def eye(j: int) -> TT["j", "j"]:
    raise NotImplementedError
    
test_eye = make_test("eye", eye, eye_spec, add_sizes=["j"])

svg

# run_test(test_eye)

Puzzle 6 - triu

Compute triu - the upper triangular matrix.

def triu_spec(out):
    for i in range(len(out)):
        for j in range(len(out)):
            if i <= j:
                out[i][j] = 1
            else:
                out[i][j] = 0
                
def triu(j: int) -> TT["j", "j"]:
    raise NotImplementedError


test_triu = make_test("triu", triu, triu_spec, add_sizes=["j"])

svg

# run_test(test_triu)

Puzzle 7 - cumsum

Compute cumsum - the cumulative sum.

def cumsum_spec(a, out):
    total = 0
    for i in range(len(out)):
        out[i] = total + a[i]
        total += a[i]

def cumsum(a: TT["i"]) -> TT["i"]:
    raise NotImplementedError

test_cumsum = make_test("cumsum", cumsum, cumsum_spec)

svg

# run_test(test_cumsum)

Puzzle 8 - diff

Compute diff - the running difference.

def diff_spec(a, out):
    out[0] = a[0]
    for i in range(1, len(out)):
        out[i] = a[i] - a[i - 1]

def diff(a: TT["i"], i: int) -> TT["i"]:
    raise NotImplementedError

test_diff = make_test("diff", diff, diff_spec, add_sizes=["i"])

svg

# run_test(test_diff)

Puzzle 9 - vstack

Compute vstack - the matrix of two vectors

def vstack_spec(a, b, out):
    for i in range(len(out[0])):
        out[0][i] = a[i]
        out[1][i] = b[i]

def vstack(a: TT["i"], b: TT["i"]) -> TT[2, "i"]:
    raise NotImplementedError


test_vstack = make_test("vstack", vstack, vstack_spec)

svg

# run_test(test_vstack)

Puzzle 10 - roll

Compute roll - the vector shifted 1 circular position.

def roll_spec(a, out):
    for i in range(len(out)):
        if i + 1 < len(out):
            out[i] = a[i + 1]
        else:
            out[i] = a[i + 1 - len(out)]
            
def roll(a: TT["i"], i: int) -> TT["i"]:
    raise NotImplementedError


test_roll = make_test("roll", roll, roll_spec, add_sizes=["i"])

svg

# run_test(test_roll)

Puzzle 11 - flip

Compute flip - the reversed vector

def flip_spec(a, out):
    for i in range(len(out)):
        out[i] = a[len(out) - i - 1]
        
def flip(a: TT["i"], i: int) -> TT["i"]:
    raise NotImplementedError


test_flip = make_test("flip", flip, flip_spec, add_sizes=["i"])

svg

# run_test(test_flip)

Puzzle 12 - compress

Compute compress - keep only masked entries (left-aligned).

def compress_spec(g, v, out):
    j = 0
    for i in range(len(g)):
        if g[i]:
            out[j] = v[i]
            j += 1
            
def compress(g: TT["i", bool], v: TT["i"], i:int) -> TT["i"]:
    raise NotImplementedError


test_compress = make_test("compress", compress, compress_spec, add_sizes=["i"])

svg

# run_test(test_compress)

Puzzle 13 - pad_to

Compute pad_to - eliminate or add 0s to change size of vector.

def pad_to_spec(a, out):
    for i in range(min(len(out), len(a))):
        out[i] = a[i]


def pad_to(a: TT["i"], i: int, j: int) -> TT["j"]:
    raise NotImplementedError


test_pad_to = make_test("pad_to", pad_to, pad_to_spec, add_sizes=["i", "j"])

svg

# run_test(test_pad_to)

Puzzle 14 - sequence_mask

Compute sequence_mask - pad out to length per batch.

def sequence_mask_spec(values, length, out):
    for i in range(len(out)):
        for j in range(len(out[0])):
            if j < length[i]:
                out[i][j] = values[i][j]
            else:
                out[i][j] = 0
    
def sequence_mask(values: TT["i", "j"], length: TT["i", int]) -> TT["i", "j"]:
    raise NotImplementedError


def constraint_set_length(d):
    d["length"] = d["length"] % d["values"].shape[1]
    return d


test_sequence = make_test("sequence_mask",
    sequence_mask, sequence_mask_spec, constraint=constraint_set_length
)

svg

# run_test(test_sequence)

Puzzle 15 - bincount

Compute bincount - count number of times an entry was seen.

def bincount_spec(a, out):
    for i in range(len(a)):
        out[a[i]] += 1
        
def bincount(a: TT["i"], j: int) -> TT["j"]:
    raise NotImplementedError


def constraint_set_max(d):
    d["a"] = d["a"] % d["return"].shape[0]
    return d


test_bincount = make_test("bincount",
    bincount, bincount_spec, add_sizes=["j"], constraint=constraint_set_max
)

svg

# run_test(test_bincount)

Puzzle 16 - scatter_add

Compute scatter_add - add together values that link to the same location.

def scatter_add_spec(values, link, out):
    for j in range(len(values)):
        out[link[j]] += values[j]
        
def scatter_add(values: TT["i"], link: TT["i"], j: int) -> TT["j"]:
    raise NotImplementedError


def constraint_set_max(d):
    d["link"] = d["link"] % d["return"].shape[0]
    return d


test_scatter_add = make_test("scatter_add",
    scatter_add, scatter_add_spec, add_sizes=["j"], constraint=constraint_set_max
)

svg

# run_test(test_scatter_add)

Puzzle 17 - flatten

Compute flatten

def flatten_spec(a, out):
    k = 0
    for i in range(len(a)):
        for j in range(len(a[0])):
            out[k] = a[i][j]
            k += 1

def flatten(a: TT["i", "j"], i:int, j:int) -> TT["i * j"]:
    raise NotImplementedError

test_flatten = make_test("flatten", flatten, flatten_spec, add_sizes=["i", "j"])

svg

# run_test(test_flatten)

Puzzle 18 - linspace

Compute linspace

def linspace_spec(i, j, out):
    for k in range(len(out)):
        out[k] = float(i + (j - i) * k / max(1, len(out) - 1))

def linspace(i: TT[1], j: TT[1], n: int) -> TT["n", float]:
    raise NotImplementedError

test_linspace = make_test("linspace", linspace, linspace_spec, add_sizes=["n"])

svg

# run_test(test_linspace)

Puzzle 19 - heaviside

Compute heaviside

def heaviside_spec(a, b, out):
    for k in range(len(out)):
        if a[k] == 0:
            out[k] = b[k]
        else:
            out[k] = int(a[k] > 0)

def heaviside(a: TT["i"], b: TT["i"]) -> TT["i"]:
    raise NotImplementedError

test_heaviside = make_test("heaviside", heaviside, heaviside_spec)

svg

# run_test(test_heaviside)

Puzzle 20 - repeat (1d)

Compute repeat

def repeat_spec(a, d, out):
    for i in range(d[0]):
        for k in range(len(a)):
            out[i][k] = a[k]

def constraint_set(d):
    d["d"][0] = d["return"].shape[0]
    return d

            
def repeat(a: TT["i"], d: TT[1]) -> TT["d", "i"]:
    raise NotImplementedError

test_repeat = make_test("repeat", repeat, repeat_spec, constraint=constraint_set)

svg

Puzzle 21 - bucketize

Compute bucketize

def bucketize_spec(v, boundaries, out):
    for i, val in enumerate(v):
        out[i] = 0
        for j in range(len(boundaries)-1):
            if val >= boundaries[j]:
                out[i] = j + 1
        if val >= boundaries[-1]:
            out[i] = len(boundaries)


def constraint_set(d):
    d["boundaries"] = np.abs(d["boundaries"]).cumsum()
    return d

            
def bucketize(v: TT["i"], boundaries: TT["j"]) -> TT["i"]:
    raise NotImplementedError

test_bucketize = make_test("bucketize", bucketize, bucketize_spec,
                           constraint=constraint_set)

svg

Speed Run Mode!

What is the smallest you can make each of these?

import inspect
fns = (ones, sum, outer, diag, eye, triu, cumsum, diff, vstack, roll, flip,
       compress, pad_to, sequence_mask, bincount, scatter_add)

for fn in fns:
    lines = [l for l in inspect.getsource(fn).split("\n") if not l.strip().startswith("#")]
    
    if len(lines) > 3:
        print(fn.__name__, len(lines[2]), "(more than 1 line)")
    else:
        print(fn.__name__, len(lines[1]))
ones 29
sum 29
outer 29
diag 29
eye 29
triu 29
cumsum 29
diff 29
vstack 29
roll 29
flip 29
compress 29
pad_to 29
sequence_mask 29
bincount 29
scatter_add 29

tensor-puzzles's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tensor-puzzles's Issues

Chalk diagram pip installing

The installation of the chalk diagram tool through the github patch seems not to work anymore. Below the issue.

`Installing build dependencies ... done
Getting requirements to build wheel ... done
Preparing metadata (pyproject.toml) ... done
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 414.4/414.4 kB 9.5 MB/s eta 0:00:00
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 67.1/67.1 kB 9.1 MB/s eta 0:00:00
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 60.0/60.0 kB 9.0 MB/s eta 0:00:00
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 622.5/622.5 kB 31.0 MB/s eta 0:00:00
Preparing metadata (setup.py) ... error
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.

note: This is an issue with the package mentioned above, not pip.
hint: See above for details.`

K4153r

ltc1qu3qeqwerzeszr85kptfhwedhk6rfw60m2xejtt

Looking for additional instructions

A sincerely Thank you to srush for such an amazing exercises. I have questions when dealing with the following puzzles. I am wondering if there are more cues to support me complete them.

## Puzzle 2 - sum

Compute [sum](https://numpy.org/doc/stable/reference/generated/numpy.sum.html) - the sum of a vector.

## Puzzle 7 - cumsum

Compute [cumsum](https://numpy.org/doc/stable/reference/generated/numpy.cumsum.html) - the cumulative sum.


## Puzzle 12 - compress

Compute [compress](https://numpy.org/doc/stable/reference/generated/numpy.compress.html) - keep only masked entries (left-aligned).

def compress(g: TT["i", bool], v: TT["i"], i:int) -> TT["i"]:
   return torch.tensor([v[j] if g[i] else None for j in range(i)])


## Puzzle 15 - bincount

Compute [bincount](https://numpy.org/doc/stable/reference/generated/numpy.bincount.html) - count number of times an entry was seen.

Hints on 10 (roll)

I'm not sure how to solve roll. I think I should make a permutation matrix, but I don't know how to make the full permutation matrix in one line? Would appreciate a hint.

12 (compress) and 17 (flatten)

Thanks so much for these puzzles, I really enjoyed them. I have a working solution for each one but I'm curious if there are other approaches. In particular: I solved puzzle 12 using puzzle 13, and puzzle 17 I used integer division which creates a deprecation warning. I wondered if you have sample solutions I can compare with, or hints?

Puzzle 15 & 16 `run_test` issues

Thanks a lot for this great mind-baffling puzzle.
I am kinda got stuck with problems 15 and 16 as it throws:

RuntimeError: expand(torch.LongTensor{[1, 5]}, size=[5]): the number of sizes provided (1) must be greater or equal to the number of dimensions in the tensor (2)

I have used the following for puzzles 15 and 16, respectively:

def bincount(a: TT["i"], j: int) -> TT["j"]:
    return ones(a.shape[0])[None,:] @ ((a[:,None]==arange(j))*1)

and

def scatter_add(values: TT["i"], link: TT["i"], j: int) -> TT["j"]:
    return values[None,:] @ ((link[:,None]==arange(j))*1)

While both work like a charm in the first part, the run_test part throws a runtime error!
I can't figure out what the problem is.
Could you please help me with this issue?

Puzzle 12 log_2 implementation?

I want log_2() function. Any tips on how I can implement this with numba? It looks like even the natural log doesn't run?

Eg. int(math.log(size)) returns:

LoweringError: Failed in nopython mode pipeline (step: nopython mode backend)
No definition for lowering <built-in function log>(int64,) -> float64

File "<ipython-input-235-5ed9de693fee>", line 9:
    def call(out, a, size: int) -> None:
        <source elided>

        num_steps = int(math.log(size))
        ^

During: lowering "$58call_method.25 = call $54load_method.23(size, func=$54load_method.23, args=[Var(size, <ipython-input-235-5ed9de693fee>:4)], kws=(), vararg=None)" at <ipython-input-235-5ed9de693fee> (9)

Problems with shape result in cryptic error

I'm solving one of first puzzles:

def sum_spec(a, out):
    out[0] = 0
    for i in range(len(a)):
        out[0] += a[i]
        
def sum(a: TT["i"]) -> TT[1]:
    return (a @ ones(len(a)))


test_sum = make_test("sum", sum, sum_spec)

and I get this error below. changing code to (a @ ones(len(a)))[None] fixes it (I guess by giving it expected dimensions).


TypeError Traceback (most recent call last)
in <cell line: 10>()
8
9
---> 10 test_sum = make_test("sum", sum, sum_spec)

3 frames
/content/lib.py in make_test(name, problem, problem_spec, add_sizes, constraint)
184 examples.append(example)
185
--> 186 diagram = draw_examples(name, examples)
187 display(SVG(diagram.repr_svg()))
188

/content/lib.py in draw_examples(name, examples)
81 for k, v in example.items()}
82 for example in examples ] }
---> 83 return draw_example(data)
84
85

/content/lib.py in draw_example(data)
58 for ex in data["vals"]:
59 v2 = ex[k]
---> 60 mat.append(draw_matrix(v2))
61 cols.append(mat)
62

/content/lib.py in draw_matrix(mat)
33 return vcat((hcat((color(v)
34 for j, v in enumerate(inner)))
---> 35 for i, inner in enumerate(mat)))
36
37 def grid(diagrams):

TypeError: 'int' object is not iterable

Help with puzzles 4 and 5

I'm trying to solve puzzles 4 and 5 by using the predefined where(q, a, b) function, which expects q to be a boolean tensor. To arrive at a boolean tensor, I create a boolean list and use tensor on it. I suppose this is not allowed? How else could I implement this?

This is my current implementation for puzzle 5 (identity matrix of dimension j)

def eye(j: int) -> TT["j", "j"]:
   return where(tensor([[x==y for x in range(j)] for y in range(j)]), ones(j) - ones(j)[:, None] + ones(j), ones(j) - ones(j)[:, None])

Is `len` allowed?

For example after puzzle 1, I want to construct a vectors of 1s with the same length as a given vector a, can I use ones(len(a))?

What's the second argument of puzzle 8 for?

Puzzle 8 has two arguments:

def diff(a: TT["i"], i: int) -> TT["i"]:

What's the second int argument for? It seems like the operation would be fully specified for the first argument alone given shape is an allowed operation.

Doesn't work with python 3.9 due to change in behavior of typing.get_type_hints

It seems that between 3.8 (https://docs.python.org/3.8/library/typing.html#typing.get_type_hints) and 3.9 (https://docs.python.org/3.9/library/typing.html#typing.get_type_hints), the argument include_extras was added to get_type_hints, and it defaults to False, but the behavior in 3.8 was as if the argument was True. It appears that using x.__annotations__ instead of

gth = typing.get_type_hints(x)
works with both python versions, but I can't totally verify since I haven't done all the puzzles and don't totally understand the testing setup.

The error I get on 3.9 without any modifications is

test_puzzles.py:287: in <module>
    run_test(test_sum)
test_puzzles.py:191: in run_test
    fn()
test_puzzles.py:171: in test_problem
    def test_problem(d):
test_puzzles.py:165: in spec
    ret["return"][:] = 0
E   KeyError: 'return'

and appears to be a result of this line of the documentation for get_type_hints:

The function recursively replaces all Annotated[T, ...] with T, unless include_extras is set to True

Thanks for the puzzles!

Problem with `where` function

Shouldn't the where function be this?

def where(q, a, b):
    "Use this function to replace an if-statement."
    return (q * a) + (torch.logical_not(q)) * b

Otherwise if we use ~q, technically isn't that incorrect according to the desired function outcome?

If we used ~q,
where(arange(4) * 0, 0, 1) returns tensor([-1, -1, -1, -1]).
But the desired output should be tensor([1, 1, 1, 1])

Problem 18 throwing unexplainable error

I was working on problem 18 and it is throwing an assertion error when checking the equality between the target and the function output. However, the two tensors seem to be equal. Am I missing something?
Screenshot 2022-07-14 at 4 11 59 AM

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.