' '

1. Notation

When creating a sampler extension, you will often be asked to write tests. Some students have trouble understanding the notation used to describe rectangles. This page explains how to interpret it.

Say we assigment mentions the rectangle

\[[0, 1] \times [2, 4]\]

The first interval \([0,1]\) denotes the range over which the rectangle extends in the X-dimension:

examplex

Similary, the second interval \([2,4]\), denotes the range in the Y-dimension:

exampley

Taking the intersection of both areas gives us the rectangle we are looking for:

examplexy

Now we need to represent this rectangle using the Rectangle2D class. Its constructor takes three parameters:

  • An Point2D origin, which must represent one of the corners of the rectangle.

  • An Vector2D X axis, which must represent one side of the rectangle.

  • An Vector2D Y axis, which must represent the other size of the rectangle.

There are multiple ways to define a single rectangle (e.g., we have 4 possible origins as the rectangle has 4 corners). One way is shown in the figure below:

example

The corresponding C++ is

Rectangle2D rectangle(Point2D(0, 2), Vector2D(1, 0), Vector2D(0, 2));

2. Writing Tests

Many students seem to have trouble finding a simple way to test certain samplers. For the sake of clarity, let’s pretend we’re dealing with 1D samplers, i.e., samplers that given an interval \([a, b]\) pick numbers from it.

Example

Take \(I = [0, 4]\).

  • The Random sampler with \(n = 4\) will pick 4 random numbers from this interval \(I\), for example 0.4, 2.9, 1.1 and 1.4.

  • The Stratified sampler with \(n = 4\) will first divide \(I\) into four subintervals \([0, 1]\), \([1, 2]\), \([2, 3]\) and \([3, 4]\). Next, it will take the center of each: \(0.5\), \(1.5\), \(2.5\) and \(3.5\).

  • The Jittered sampler with \(n = 4\) will first divide \(I\) into four subintervals \([0, 1]\), \([1, 2]\), \([2, 3]\) and \([3, 4]\). Next, it will take one random point from each: \(0.3\), \(1.7\), \(2.1\) and \(3.5\).

2.1. Random Sampler

Testing the random sampler is rather straightforward:

def test_random_sampler(n, sampled_interval):
    sampler = RandomSampler(n)
    samples = sampler.sample(sampled_interval)

    assert(len(samples) == n)

    for sample in samples:
        assert sample in sampled_interval

2.2. Stratified Sampler

In the case of this sampler, you know exactly which points should be returned.

def test_stratified_sampler(n, sampled_intervals, expected_samples):
    sampler = StratifiedSampler(n)
    samples = sampler.sample(sampled_intervals)

    assert set(samples) == set(expected_samples)
Note

Implementing the same logic in C++ might be a bit harder.

  • You will probably not be able to rely on sets, so you’ll have to manually check if for every sample in samples there is a corresponding one in expected_samples.

  • Rounding errors might cause problems. Do not compare Point2Ds directly using p == q, instead use p == approx(q).

2.3. Jittered & Half-Jittered Samplers

Let’s now consider the Jittered and Half-Jittered samplers.

When writing a test for these samplers, you know exactly how many samples it should return. Start with checking that the sample count is correct. Next, determine the subintervals and check that for each subinterval you can find a sample. If a sampler satisfies both these conditions, you know it works.

def test_sampler(sampler, sampled_interval, expected_intervals):
    samples = sampler.sample(sampled_interval)

    assert len(samples) == len(expected_intervals)

    for expected_interval in expected_intervals:
        assert any(expected_interval.contains(sample) for sample in samples)


test_sampler(sampler=JitteredSampler(n=4),
             sampled_interval=(0, 4),
             expected_intervals=[(0, 1), (1, 2), (2, 3), (3, 4)])

2.4. N-Rooks

With N-Rooks, there are no clear subintervals in which the samples should fall. However, we can zoom in on the coordinates and check that these fall into certain intervals.

def test_nrooks(n, sampled_rectangle):
    x_subintervals = sampled_rectangle.x_interval.divide_evenly(n)
    y_subintervals = sampled_rectangle.y_interval.divide_evenly(n)
    samples = NRooks(n).sample(sampled_rectangle)

    for interval in x_subintervals:
        assert any(interval.contains(sample.x) for sample in samples)

    for interval in y_subintervals:
        assert any(interval.contains(sample.y) for sample in samples)