| """ |
| Unit tests for all transforms in itt_solver.transforms. |
| |
| Usage: |
| python tests/test_transforms.py |
| |
| 40 tests covering: Kronecker, mirror tiles, upscale, downscale, stack, |
| rotate, reflect, color ops, gravity, crop, transpose, shifted tile, |
| fill enclosed. |
| """ |
| import numpy as np |
| from itt_solver import transforms as tr |
|
|
| INP = np.array([[0,7,7],[7,7,7],[0,7,7]], dtype=float) |
|
|
| tests_passed = 0 |
| tests_failed = 0 |
|
|
| def check(name, condition): |
| global tests_passed, tests_failed |
| if condition: |
| print(f" ✅ {name}") |
| tests_passed += 1 |
| else: |
| print(f" ❌ {name}") |
| tests_failed += 1 |
|
|
| print("=== Kronecker Self-Similar ===") |
| T = tr.KroneckerSelfSimilar() |
| out = T.apply(INP) |
| check("Output shape is 9x9", out.shape == (9, 9)) |
| check("σ=0 vs known target", np.array_equal(out, np.kron((INP!=0).astype(float), INP))) |
|
|
| print("\n=== KroneckerSelfSimilarInv ===") |
| T = tr.KroneckerSelfSimilarInv() |
| out = T.apply(INP) |
| check("Output shape is 9x9", out.shape == (9, 9)) |
|
|
| print("\n=== MirrorTileH ===") |
| T = tr.MirrorTileH() |
| out = T.apply(INP) |
| check("Shape is 3x6", out.shape == (3, 6)) |
| check("Left half is input", np.array_equal(out[:, :3], INP)) |
| check("Right half is fliplr(input)", np.array_equal(out[:, 3:], np.fliplr(INP))) |
|
|
| print("\n=== MirrorTileV ===") |
| T = tr.MirrorTileV() |
| out = T.apply(INP) |
| check("Shape is 6x3", out.shape == (6, 3)) |
| check("Top half is input", np.array_equal(out[:3, :], INP)) |
| check("Bottom half is flipud(input)", np.array_equal(out[3:, :], np.flipud(INP))) |
|
|
| print("\n=== MirrorTile4Way ===") |
| T = tr.MirrorTile4Way() |
| out = T.apply(INP) |
| check("Shape is 6x6", out.shape == (6, 6)) |
|
|
| print("\n=== Upscale 2x ===") |
| T = tr.Upscale(2) |
| out = T.apply(INP) |
| check("Shape is 6x6", out.shape == (6, 6)) |
| check("Top-left 2x2 block is INP[0,0]", np.all(out[:2, :2] == INP[0, 0])) |
|
|
| print("\n=== Upscale 3x ===") |
| T = tr.Upscale(3) |
| out = T.apply(INP) |
| check("Shape is 9x9", out.shape == (9, 9)) |
| check("Top-left 3x3 block is INP[0,0]", np.all(out[:3, :3] == INP[0, 0])) |
|
|
| print("\n=== Downscale 2x ===") |
| T = tr.Downscale(2) |
| big = np.kron(INP, np.ones((2, 2))) |
| out = T.apply(big) |
| check("Downscale of upscaled recovers original", np.array_equal(out, INP)) |
|
|
| print("\n=== StackH 3 ===") |
| T = tr.StackH(3) |
| out = T.apply(INP) |
| check("Shape is 3x9", out.shape == (3, 9)) |
| check("First third is input", np.array_equal(out[:, :3], INP)) |
|
|
| print("\n=== StackV 3 ===") |
| T = tr.StackV(3) |
| out = T.apply(INP) |
| check("Shape is 9x3", out.shape == (9, 3)) |
| check("First third is input", np.array_equal(out[:3, :], INP)) |
|
|
| print("\n=== Rotate 90/180/270 ===") |
| for k in [1, 2, 3]: |
| T = tr.Rotate(k) |
| out = T.apply(INP) |
| check(f"Rotate_{90*k} matches np.rot90", np.array_equal(out, np.rot90(INP, k))) |
|
|
| print("\n=== Reflect h/v ===") |
| T = tr.Reflect('h') |
| check("Reflect_h matches flipud", np.array_equal(T.apply(INP), np.flipud(INP))) |
| T = tr.Reflect('v') |
| check("Reflect_v matches fliplr", np.array_equal(T.apply(INP), np.fliplr(INP))) |
|
|
| print("\n=== RetainColor ===") |
| T = tr.RetainColor(7) |
| out = T.apply(INP) |
| check("Only 7s remain", np.all(out[INP == 7] == 7)) |
| check("Non-7 positions are 0", np.all(out[INP != 7] == 0)) |
|
|
| print("\n=== RemoveColor ===") |
| T = tr.RemoveColor(7) |
| out = T.apply(INP) |
| check("7s are removed", np.all(out[INP == 7] == 0)) |
| check("0s stay 0", np.all(out[INP == 0] == 0)) |
|
|
| print("\n=== InvertColors ===") |
| T = tr.InvertColors() |
| out = T.apply(INP) |
| check("0→7 swap", np.all(out[INP == 0] == 7)) |
| check("7→0 swap", np.all(out[INP == 7] == 0)) |
|
|
| print("\n=== GravityDown ===") |
| T = tr.GravityDown() |
| col_in = np.array([[0,7,0],[0,0,7],[7,0,0]], dtype=float) |
| out = T.apply(col_in) |
| check("Col 0: 7 at bottom", out[2, 0] == 7 and out[0, 0] == 0 and out[1, 0] == 0) |
| check("Col 1: 7 at bottom", out[2, 1] == 7 and out[0, 1] == 0) |
|
|
| print("\n=== GravityUp ===") |
| T = tr.GravityUp() |
| out = T.apply(col_in) |
| check("Col 0: 7 at top", out[0, 0] == 7 and out[1, 0] == 0 and out[2, 0] == 0) |
|
|
| print("\n=== CropToContent ===") |
| T = tr.CropToContent() |
| padded = np.array([[0,0,0,0],[0,7,7,0],[0,7,7,0],[0,0,0,0]], dtype=float) |
| out = T.apply(padded) |
| check("Crops to 2x2", out.shape == (2, 2)) |
| check("All 7s", np.all(out == 7)) |
|
|
| print("\n=== Transpose ===") |
| T = tr.Transpose() |
| out = T.apply(INP) |
| check("Shape is transposed", out.shape == (3, 3)) |
| check("Values match transpose", np.array_equal(out, INP.T)) |
|
|
| print("\n=== ShiftedTile ===") |
| T = tr.tile_to_target_shifted(shift=(1, 1), tile_factor=3) |
| out = T.apply(INP) |
| check("Shape is 9x9", out.shape == (9, 9)) |
| check("Differs from vanilla tile", not np.array_equal(out, np.tile(INP, (3, 3)))) |
|
|
| print("\n=== FillEnclosedHarmonic ===") |
| T = tr.FillEnclosedHarmonic() |
| enclosed = np.array([[7,7,7],[7,0,7],[7,7,7]], dtype=float) |
| out = T.apply(enclosed) |
| check("Center hole filled", out[1, 1] == 7) |
|
|
| print(f"\n{'='*50}") |
| print(f"Results: {tests_passed} passed, {tests_failed} failed") |
|
|