#!/usr/bin/python3

# Halide tutorial lesson 6.

# This lesson demonstrates how to evaluate a hl.Func over a domain that
# does not start at (0, 0).

# This lesson can be built by invoking the command:
#    make test_tutorial_lesson_06_realizing_over_shifted_domains
# in a shell with the current directory at python_bindings/

import halide as hl


def main():

    # The last lesson was quite involved, and scheduling complex
    # multi-stage pipelines is ahead of us. As an interlude, let's
    # consider something easy: evaluating funcs over rectangular
    # domains that do not start at the origin.

    # We define our familiar gradient function.
    gradient = hl.Func("gradient")
    x, y = hl.Var("x"), hl.Var("y")
    gradient[x, y] = x + y

    # And turn on tracing so we can see how it is being evaluated.
    gradient.trace_stores()

    # Previously we've realized gradient like so:
    #
    # gradient.realize([8, 8])
    #
    # This does three things internally:
    # 1) Generates code than can evaluate gradient over an arbitrary
    # rectangle.
    # 2) Allocates a new 8 x 8 image.
    # 3) Runs the generated code to evaluate gradient for all x, y
    # from (0, 0) to (7, 7) and puts the result into the image.
    # 4) Returns the new image as the result of the realize call.

    # What if we're managing memory carefully and don't want Halide
    # to allocate a new image for us? We can call realize another
    # way. We can pass it an image we would like it to fill in. The
    # following evaluates our hl.Func into an existing image:
    print("Evaluating gradient from (0, 0) to (7, 7)")
    result = hl.Buffer(hl.Int(32), [8, 8])
    gradient.realize(result)

    # Let's check it did what we expect:
    for yy in range(8):
        for xx in range(8):
            assert result[xx, yy] == xx + yy, "Something went wrong!"

    # Now let's evaluate gradient over a 5 x 7 rectangle that starts
    # somewhere else -- at position (100, 50). So x and y will run
    # from (100, 50) to (104, 56) inclusive.

    # We start by creating an image that represents that rectangle:
    # In the constructor we tell it the size.
    shifted = hl.Buffer(hl.Int(32), [5, 7])
    shifted.set_min([100, 50])  # Then we tell it the top-left corner.

    print("Evaluating gradient from (100, 50) to (104, 56)")

    # Note that this won't need to compile any new code, because when
    # we realized it the first time, we generated code capable of
    # evaluating gradient over an arbitrary rectangle.
    gradient.realize(shifted)

    # From C++, we also access the image object using coordinates
    # that start at (100, 50).
    for yy in range(50, 57):
        for xx in range(100, 105):
            assert shifted[xx, yy] == xx + yy, "Something went wrong!"

    # The image 'shifted' stores the value of our hl.Func over a domain
    # that starts at (100, 50), so asking for shifted(0, 0) would in
    # fact read out-of-bounds and probably crash.

    # What if we want to evaluate our hl.Func over some region that
    # isn't rectangular? Too bad. Halide only does rectangles :)

    print("Success!")
    return 0


if __name__ == "__main__":
    main()
