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
The first interval \([0,1]\) denotes the range over which the rectangle extends in the X-dimension:

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

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

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:

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]\).
|
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.
|
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)