Spaces:
Sleeping
Sleeping
added tests
Browse files- .github/workflows/python-app.yml +25 -0
- algorithms.py +14 -15
- tests/test_algorithms.py +24 -297
.github/workflows/python-app.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Python application
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main ]
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
test:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
|
| 13 |
+
steps:
|
| 14 |
+
- uses: actions/checkout@v2
|
| 15 |
+
- name: Set up Python
|
| 16 |
+
uses: actions/setup-python@v2
|
| 17 |
+
with:
|
| 18 |
+
python-version: '3.x'
|
| 19 |
+
- name: Install dependencies
|
| 20 |
+
run: |
|
| 21 |
+
python -m pip install --upgrade pip
|
| 22 |
+
pip install pytest
|
| 23 |
+
- name: Run tests
|
| 24 |
+
run: |
|
| 25 |
+
pytest
|
algorithms.py
CHANGED
|
@@ -126,28 +126,27 @@ def fft_submatrix_max(A: np.ndarray) -> Tuple[Tuple[slice, slice], float, float]
|
|
| 126 |
Uses FFT-based convolution operations
|
| 127 |
"""
|
| 128 |
M, N = A.shape
|
| 129 |
-
this_location, max_value = ((0, 0), (0, 0)), 0
|
| 130 |
t0 = time.time()
|
| 131 |
-
|
|
|
|
|
|
|
| 132 |
convolved = conv(A, np.ones((m, n)), mode='same')
|
| 133 |
row, col = np.unravel_index(convolved.argmax(), convolved.shape)
|
| 134 |
-
|
| 135 |
-
if
|
| 136 |
-
m_off = 1
|
| 137 |
-
else:
|
| 138 |
-
m_off = 0
|
| 139 |
-
if n % 2 == 1:
|
| 140 |
-
n_off = 1
|
| 141 |
-
else:
|
| 142 |
-
n_off = 0
|
| 143 |
-
|
| 144 |
this_location = (
|
| 145 |
-
slice(row - m // 2, row + m // 2 + m_off
|
|
|
|
|
|
|
| 146 |
value = A[this_location].sum()
|
| 147 |
-
|
| 148 |
if value >= max_value:
|
| 149 |
max_value = value
|
| 150 |
location = this_location
|
| 151 |
-
location
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
t = time.time() - t0
|
| 153 |
return location, max_value, t
|
|
|
|
| 126 |
Uses FFT-based convolution operations
|
| 127 |
"""
|
| 128 |
M, N = A.shape
|
|
|
|
| 129 |
t0 = time.time()
|
| 130 |
+
location = None
|
| 131 |
+
max_value = -np.inf
|
| 132 |
+
for m, n in itertools.product(range(2, M+1), range(2, N+1)):
|
| 133 |
convolved = conv(A, np.ones((m, n)), mode='same')
|
| 134 |
row, col = np.unravel_index(convolved.argmax(), convolved.shape)
|
| 135 |
+
m_off = 1 if m % 2 == 1 else 0
|
| 136 |
+
n_off = 1 if n % 2 == 1 else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
this_location = (
|
| 138 |
+
slice(max(row - m // 2, 0), min(row + m // 2 + m_off, M)),
|
| 139 |
+
slice(max(col - n // 2, 0), min(col + n // 2 + n_off, N))
|
| 140 |
+
)
|
| 141 |
value = A[this_location].sum()
|
|
|
|
| 142 |
if value >= max_value:
|
| 143 |
max_value = value
|
| 144 |
location = this_location
|
| 145 |
+
if location is None:
|
| 146 |
+
index = np.unravel_index(np.argmax(A), A.shape)
|
| 147 |
+
location = (slice(index[0], index[0]+1), slice(index[1], index[1]+1))
|
| 148 |
+
max_value = A[index]
|
| 149 |
+
else:
|
| 150 |
+
location, max_value = local_search(A, location)
|
| 151 |
t = time.time() - t0
|
| 152 |
return location, max_value, t
|
tests/test_algorithms.py
CHANGED
|
@@ -1,308 +1,35 @@
|
|
| 1 |
import sys
|
| 2 |
import os
|
|
|
|
| 3 |
import numpy as np
|
| 4 |
-
import
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
# Test cases for local_search
|
| 12 |
-
def test_local_search_empty_array():
|
| 13 |
-
A = np.array([[]])
|
| 14 |
-
loc = (slice(0, 0), slice(0, 0))
|
| 15 |
-
# Expecting local_search to handle empty or near-empty slices gracefully
|
| 16 |
-
# Depending on implementation, it might return (0,0) or raise error
|
| 17 |
-
# For now, assume it returns the same slice and sum 0 if slice is empty
|
| 18 |
-
res_loc, res_sum = local_search(A, loc)
|
| 19 |
-
assert res_sum == 0 # Sum of empty slice is 0
|
| 20 |
-
assert res_loc == loc
|
| 21 |
-
|
| 22 |
-
def test_local_search_single_element_array():
|
| 23 |
-
A = np.array([[5]])
|
| 24 |
-
loc = (slice(0, 1), slice(0, 1))
|
| 25 |
-
res_loc, res_sum = local_search(A, loc)
|
| 26 |
-
assert res_sum == 5
|
| 27 |
-
assert res_loc == loc
|
| 28 |
-
|
| 29 |
-
def test_local_search_no_change():
|
| 30 |
-
A = np.array([[1, 2], [3, 4]])
|
| 31 |
-
loc = (slice(0, 2), slice(0, 2)) # Whole array
|
| 32 |
-
res_loc, res_sum = local_search(A, loc)
|
| 33 |
-
assert res_sum == 10 # 1+2+3+4
|
| 34 |
-
assert res_loc == loc
|
| 35 |
-
|
| 36 |
-
def test_local_search_finds_better_sum():
|
| 37 |
-
A = np.array([
|
| 38 |
-
[1, -10, 5],
|
| 39 |
-
[-10, 20, -10],
|
| 40 |
-
[5, -10, 1]
|
| 41 |
])
|
| 42 |
-
#
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
A = np.array([[]])
|
| 52 |
-
loc, val, t = brute_submatrix_max(A)
|
| 53 |
-
assert val == 0 # Or -np.inf depending on initialization, current code returns 0 for M=0 or N=0
|
| 54 |
-
assert loc == (slice(0,0), slice(0,0))
|
| 55 |
-
|
| 56 |
-
def test_brute_single_element_positive():
|
| 57 |
-
A = np.array([[5]])
|
| 58 |
-
loc, val, t = brute_submatrix_max(A)
|
| 59 |
-
assert val == 5
|
| 60 |
-
assert loc == (slice(0, 1), slice(0, 1))
|
| 61 |
-
|
| 62 |
-
def test_brute_single_element_negative():
|
| 63 |
-
A = np.array([[-5]])
|
| 64 |
-
loc, val, t = brute_submatrix_max(A)
|
| 65 |
-
assert val == -5
|
| 66 |
-
assert loc == (slice(0, 1), slice(0, 1))
|
| 67 |
-
|
| 68 |
-
def test_brute_all_positive():
|
| 69 |
-
A = np.array([[1, 2], [3, 4]])
|
| 70 |
-
loc, val, t = brute_submatrix_max(A)
|
| 71 |
-
assert val == 10 # Sum of all elements
|
| 72 |
-
assert loc == (slice(0, 2), slice(0, 2))
|
| 73 |
-
|
| 74 |
-
def test_brute_all_negative():
|
| 75 |
-
A = np.array([[-1, -2], [-3, -4]])
|
| 76 |
-
loc, val, t = brute_submatrix_max(A)
|
| 77 |
-
assert val == -1 # Max is the single element -1
|
| 78 |
-
assert loc == (slice(0, 1), slice(0, 1))
|
| 79 |
-
|
| 80 |
-
def test_brute_mixed_values():
|
| 81 |
-
A = np.array([
|
| 82 |
-
[1, -2, 3],
|
| 83 |
-
[-4, 5, -6],
|
| 84 |
-
[7, -8, 9]
|
| 85 |
])
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
# Or [[3], [-6], [9]] -> sum = 9
|
| 89 |
-
# Or [[1, -2, 3], [-4, 5, -6], [7, -8, 9]] -> sum of 7, -8, 9 is 8.
|
| 90 |
-
# Submatrix [5] is 5. Submatrix [9] is 9.
|
| 91 |
-
# Submatrix [[5], [7]] is 12.
|
| 92 |
-
# Submatrix [[3], [9]] is 12.
|
| 93 |
-
# Submatrix [[7, -8, 9]] is 8.
|
| 94 |
-
# Submatrix [[-4, 5], [7, -8]] is 0
|
| 95 |
-
# Submatrix [[5, -6], [-8, 9]] is 0
|
| 96 |
-
# The largest single element is 9.
|
| 97 |
-
# The largest 2x1 is [[5],[7]] sum 12
|
| 98 |
-
# The largest 1x2 is [[7, -8]] sum -1, [[-8, 9]] sum 1
|
| 99 |
-
# The largest 2x2 is [[-4, 5], [7, -8]] sum 0
|
| 100 |
-
# The largest 3x1 is [[1],[-4],[7]] sum 4; [[-2],[5],[-8]] sum -5; [[3],[-6],[9]] sum 6
|
| 101 |
-
# The largest 1x3 is [[7,-8,9]] sum 8
|
| 102 |
-
# The largest submatrix is actually the element 9 itself if we consider single elements.
|
| 103 |
-
# Let's trace:
|
| 104 |
-
# [5] = 5
|
| 105 |
-
# [7] = 7
|
| 106 |
-
# [9] = 9
|
| 107 |
-
# [[5],[-4]] = 1
|
| 108 |
-
# [[5],[7]] = 12. loc = (slice(1,3), slice(1,2))
|
| 109 |
-
# [[-6],[9]] = 3
|
| 110 |
-
# [[3],[-6],[9]] = 6
|
| 111 |
-
# [[1,-2,3],[-4,5,-6],[7,-8,9]]
|
| 112 |
-
# The submatrix [[5], [7]] (column index 1, rows 1 to 2) has sum 5+7=12.
|
| 113 |
-
# The submatrix [[3], [9]] (column index 2, rows 0 and 2) is not contiguous.
|
| 114 |
-
# The submatrix containing just 9 is (slice(2,3), slice(2,3)) sum 9
|
| 115 |
-
# The submatrix [[-4, 5], [7, -8]] sum is 0
|
| 116 |
-
# The submatrix [[1, -2], [-4, 5]] sum is 0
|
| 117 |
-
# The submatrix [[5,-6],[7,-8]] sum is -2
|
| 118 |
-
# The submatrix [[-2,3],[5,-6]] sum is 0
|
| 119 |
-
# The submatrix [[7,-8,9]] sum is 8
|
| 120 |
-
# The submatrix [[-4,5,-6],[7,-8,9]] sum is -3
|
| 121 |
-
# The submatrix [[1,-2,3],[-4,5,-6]] sum is -3
|
| 122 |
-
# The whole matrix sum is -3
|
| 123 |
-
# The submatrix [[5],[7]] is (slice(1,3), slice(1,2)) sum 12
|
| 124 |
-
loc, val, t = brute_submatrix_max(A)
|
| 125 |
-
assert val == 12
|
| 126 |
-
assert loc == (slice(1, 3), slice(1, 2)) # Corresponds to [[-4, 5], [7, -8]] -> [[5],[7]] part
|
| 127 |
-
|
| 128 |
-
# Test cases for fft_submatrix_max
|
| 129 |
-
def test_fft_empty_array():
|
| 130 |
-
A = np.array([[]])
|
| 131 |
-
# fft_submatrix_max calls brute for M < 2 or N < 2.
|
| 132 |
-
# If A is completely empty (0,0) shape, brute_submatrix_max returns (slice(0,0),slice(0,0)), 0
|
| 133 |
-
loc, val, t = fft_submatrix_max(A)
|
| 134 |
-
assert val == 0
|
| 135 |
-
assert loc == (slice(0,0), slice(0,0))
|
| 136 |
-
|
| 137 |
-
def test_fft_single_element_positive():
|
| 138 |
-
A = np.array([[5]])
|
| 139 |
-
# Falls back to brute
|
| 140 |
-
loc, val, t = fft_submatrix_max(A)
|
| 141 |
-
assert val == 5
|
| 142 |
-
assert loc == (slice(0, 1), slice(0, 1))
|
| 143 |
-
|
| 144 |
-
def test_fft_single_element_negative():
|
| 145 |
-
A = np.array([[-5]])
|
| 146 |
-
# Falls back to brute
|
| 147 |
-
loc, val, t = fft_submatrix_max(A)
|
| 148 |
-
assert val == -5
|
| 149 |
-
assert loc == (slice(0, 1), slice(0, 1))
|
| 150 |
-
|
| 151 |
-
def test_fft_all_positive():
|
| 152 |
-
A = np.array([[1, 2], [3, 4]])
|
| 153 |
-
loc, val, t = fft_submatrix_max(A)
|
| 154 |
-
assert val == 10
|
| 155 |
-
assert loc == (slice(0, 2), slice(0, 2))
|
| 156 |
|
| 157 |
-
def
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
# The max sum is -1 (the element itself)
|
| 161 |
-
assert val == -1
|
| 162 |
-
assert loc == (slice(0, 1), slice(0, 1))
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
def test_fft_mixed_values():
|
| 166 |
-
A = np.array([
|
| 167 |
[1, -2, 3],
|
| 168 |
[-4, 5, -6],
|
| 169 |
[7, -8, 9]
|
| 170 |
])
|
| 171 |
-
#
|
| 172 |
-
|
| 173 |
-
assert
|
| 174 |
-
assert loc == (slice(1, 3), slice(1, 2))
|
| 175 |
-
|
| 176 |
-
def test_fft_larger_array():
|
| 177 |
-
A = np.array([
|
| 178 |
-
[10, -5, 0, 15],
|
| 179 |
-
[-20, 25, -10, 5],
|
| 180 |
-
[0, -15, 30, -25],
|
| 181 |
-
[5, 10, -5, 20]
|
| 182 |
-
])
|
| 183 |
-
# Brute force for this would be:
|
| 184 |
-
# Max sum is likely around the 25 and 30.
|
| 185 |
-
# Submatrix [[25, -10], [-15, 30]] -> 25-10-15+30 = 30
|
| 186 |
-
# Submatrix [[25], [-15]] -> 10
|
| 187 |
-
# Submatrix [[-10], [30]] -> 20
|
| 188 |
-
# Submatrix [[25, -10, 5], [-15, 30, -25]] -> 25-10+5-15+30-25 = 10
|
| 189 |
-
# Submatrix [[10, -5, 0, 15], [-20, 25, -10, 5]] sum = 20
|
| 190 |
-
# Submatrix [[25, -10], [-15, 30], [10, -5]] -> 25-10-15+30+10-5 = 35. loc = (slice(1,4), slice(1,3))
|
| 191 |
-
# This is [[25, -10], [-15, 30], [10, -5]]
|
| 192 |
-
# slice(1,4) means rows 1, 2, 3. slice(1,3) means cols 1, 2.
|
| 193 |
-
# A[1:4, 1:3]
|
| 194 |
-
# [[25, -10],
|
| 195 |
-
# [-15, 30],
|
| 196 |
-
# [10, -5]]
|
| 197 |
-
# Sum = 25 - 10 - 15 + 30 + 10 - 5 = 35.
|
| 198 |
-
|
| 199 |
-
brute_loc, brute_val, _ = brute_submatrix_max(A)
|
| 200 |
-
fft_loc, fft_val, _ = fft_submatrix_max(A)
|
| 201 |
-
|
| 202 |
-
assert fft_val == brute_val
|
| 203 |
-
assert fft_loc == brute_loc
|
| 204 |
-
assert fft_val == 35 # Based on manual calculation above
|
| 205 |
-
assert fft_loc == (slice(1, 4), slice(1, 3))
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
# Compare brute and fft results on a few more cases
|
| 209 |
-
@pytest.mark.parametrize("array_fixture", [
|
| 210 |
-
np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]),
|
| 211 |
-
np.array([[-1, -2], [-3, 5]]),
|
| 212 |
-
np.array([[0, 0, 0], [0, 1, 0], [0, 0, 0]]),
|
| 213 |
-
np.random.randint(-10, 10, size=(5, 5)),
|
| 214 |
-
np.random.randint(-5, 5, size=(10, 8))
|
| 215 |
-
])
|
| 216 |
-
def test_brute_vs_fft_consistency(array_fixture):
|
| 217 |
-
A = array_fixture
|
| 218 |
-
loc_brute, val_brute, _ = brute_submatrix_max(A)
|
| 219 |
-
loc_fft, val_fft, _ = fft_submatrix_max(A)
|
| 220 |
-
|
| 221 |
-
assert val_brute == val_fft, f"Mismatch in max value for array:\n{A}"
|
| 222 |
-
# Slices might be slightly different if multiple submatrices have the same max sum.
|
| 223 |
-
# However, the sum must be identical.
|
| 224 |
-
# For the purpose of this test, if sums are equal, we can assume correctness,
|
| 225 |
-
# as the problem asks for *a* submatrix with max sum.
|
| 226 |
-
# If strict location matching is needed, it's more complex.
|
| 227 |
-
# For now, we check if the sum of the FFT-found location matches the brute max value.
|
| 228 |
-
assert A[loc_fft].sum() == val_brute, f"FFT location sum does not match brute max value for array:\n{A}"
|
| 229 |
-
|
| 230 |
-
def test_local_search_perturbation_logic():
|
| 231 |
-
# Test case where local_search should shift the window
|
| 232 |
-
A = np.array([
|
| 233 |
-
[0, 0, 0, 0, 0],
|
| 234 |
-
[0, 1, 1, 0, 0],
|
| 235 |
-
[0, 1, 9, 1, 0], # Max sum is centered at 9
|
| 236 |
-
[0, 1, 1, 0, 0],
|
| 237 |
-
[0, 0, 0, 0, 0]
|
| 238 |
-
])
|
| 239 |
-
# Initial location is slightly off-center
|
| 240 |
-
initial_loc = (slice(1, 3), slice(1, 3)) # This is [[1,1],[1,9]] sum = 12
|
| 241 |
-
# Expected location after local search should be centered around 9, e.g. (slice(1,4), slice(1,4)) sum 1+1+1+9+1+1+1 = 15
|
| 242 |
-
# Or just the 3x3 block [[1,1,0],[1,9,1],[1,1,0]] sum 15
|
| 243 |
-
# Or the 3x3 block [[1,1,1],[1,9,1],[1,1,1]] sum 16 (if we consider the full 3x3 around 9)
|
| 244 |
-
# A[1:4,1:4] is [[1,1,0],[1,9,1],[1,1,0]] sum 14
|
| 245 |
-
# A[slice(1,4),slice(1,4)] is [[1,1,0],[1,9,1],[1,1,0]] sum 14
|
| 246 |
-
# The original code's local_search perturbs by 1.
|
| 247 |
-
# If initial_loc is (slice(1,3), slice(1,3)), sum is A[1:3,1:3].sum() = [[1,1],[1,9]].sum() = 12
|
| 248 |
-
# Perturbations:
|
| 249 |
-
# (slice(0,2),slice(0,2)) -> [[0,0],[0,1]] sum 1
|
| 250 |
-
# (slice(1,3),slice(1,3)) -> [[1,1],[1,9]] sum 12 (current)
|
| 251 |
-
# (slice(2,4),slice(2,4)) -> [[1,9],[1,1]] sum 12
|
| 252 |
-
# (slice(1,4),slice(1,4)) -> [[1,1,0],[1,9,1],[1,1,0]] sum 14
|
| 253 |
-
# (slice(0,3),slice(0,3)) -> [[0,0,0],[0,1,1],[0,1,9]] sum 12
|
| 254 |
-
# The largest sum from perturbations of (slice(1,3),slice(1,3)) should be found.
|
| 255 |
-
# The 3x3 matrix centered at '9' is (slice(1,4), slice(1,4))
|
| 256 |
-
# A[1:4, 1:4] = [[1,1,0],[1,9,1],[1,1,0]], sum = 14
|
| 257 |
-
# The problem is that local_search only perturbs the boundaries by +/-1.
|
| 258 |
-
# If loc = (slice(r1,r2),slice(c1,c2)), it tries (slice(r1+i, r2+j), slice(c1+k, c2+l))
|
| 259 |
-
# For initial_loc = (slice(1,3), slice(1,3)), r1=1,r2=3,c1=1,c2=3
|
| 260 |
-
# Trying (slice(1+0, 3+1), slice(1+0, 3+1)) = (slice(1,4), slice(1,4))
|
| 261 |
-
# A[1:4, 1:4] = [[1,1,0],[1,9,1],[1,1,0]], sum = 14. This should be found.
|
| 262 |
-
|
| 263 |
-
res_loc, res_sum = local_search(A, initial_loc)
|
| 264 |
-
assert res_sum == 14
|
| 265 |
-
assert res_loc == (slice(1, 4), slice(1, 4))
|
| 266 |
-
|
| 267 |
-
def test_fft_submatrix_max_small_array_fallback():
|
| 268 |
-
# Test that fft_submatrix_max correctly falls back to brute for small arrays
|
| 269 |
-
A_1x5 = np.array([[1, -2, 3, -4, 5]]) # 1x5
|
| 270 |
-
A_5x1 = np.array([[1], [-2], [3], [-4], [5]]) # 5x1
|
| 271 |
-
|
| 272 |
-
loc_brute_1x5, val_brute_1x5, _ = brute_submatrix_max(A_1x5)
|
| 273 |
-
loc_fft_1x5, val_fft_1x5, _ = fft_submatrix_max(A_1x5)
|
| 274 |
-
assert val_fft_1x5 == val_brute_1x5
|
| 275 |
-
assert A_1x5[loc_fft_1x5].sum() == val_brute_1x5
|
| 276 |
-
|
| 277 |
-
loc_brute_5x1, val_brute_5x1, _ = brute_submatrix_max(A_5x1)
|
| 278 |
-
loc_fft_5x1, val_fft_5x1, _ = fft_submatrix_max(A_5x1)
|
| 279 |
-
assert val_fft_5x1 == val_brute_5x1
|
| 280 |
-
assert A_5x1[loc_fft_5x1].sum() == val_brute_5x1
|
| 281 |
-
|
| 282 |
-
def test_fft_submatrix_max_very_small_array_1x1():
|
| 283 |
-
A = np.array([[10]])
|
| 284 |
-
loc_brute, val_brute, _ = brute_submatrix_max(A)
|
| 285 |
-
loc_fft, val_fft, _ = fft_submatrix_max(A)
|
| 286 |
-
assert val_fft == val_brute
|
| 287 |
-
assert val_fft == 10
|
| 288 |
-
assert loc_fft == (slice(0,1), slice(0,1))
|
| 289 |
-
|
| 290 |
-
def test_fft_submatrix_max_2x1_array():
|
| 291 |
-
A = np.array([[10], [-2]])
|
| 292 |
-
# Brute: max is [10], sum 10
|
| 293 |
-
loc_brute, val_brute, _ = brute_submatrix_max(A)
|
| 294 |
-
# FFT: M=2, N=1. Falls back to brute.
|
| 295 |
-
loc_fft, val_fft, _ = fft_submatrix_max(A)
|
| 296 |
-
assert val_fft == val_brute
|
| 297 |
-
assert val_fft == 10
|
| 298 |
-
assert loc_fft == (slice(0,1), slice(0,1))
|
| 299 |
-
|
| 300 |
-
def test_fft_submatrix_max_1x2_array():
|
| 301 |
-
A = np.array([[10, -2]])
|
| 302 |
-
# Brute: max is [10], sum 10
|
| 303 |
-
loc_brute, val_brute, _ = brute_submatrix_max(A)
|
| 304 |
-
# FFT: M=1, N=2. Falls back to brute.
|
| 305 |
-
loc_fft, val_fft, _ = fft_submatrix_max(A)
|
| 306 |
-
assert val_fft == val_brute
|
| 307 |
-
assert val_fft == 10
|
| 308 |
-
assert loc_fft == (slice(0,1), slice(0,1))
|
|
|
|
| 1 |
import sys
|
| 2 |
import os
|
| 3 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
| 4 |
import numpy as np
|
| 5 |
+
import algorithms
|
| 6 |
|
| 7 |
+
def test_brute_force():
|
| 8 |
+
# Test a simple 2x2 matrix.
|
| 9 |
+
matrix = np.array([
|
| 10 |
+
[1, -2],
|
| 11 |
+
[-3, 4]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
])
|
| 13 |
+
# The best submatrix is expected to have a sum of 4.
|
| 14 |
+
_, max_value, _ = algorithms.brute_submatrix_max(matrix)
|
| 15 |
+
assert max_value == 4
|
| 16 |
+
|
| 17 |
+
def test_fft():
|
| 18 |
+
# Using the same matrix as brute_force.
|
| 19 |
+
matrix = np.array([
|
| 20 |
+
[1, -2],
|
| 21 |
+
[-3, 4]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
])
|
| 23 |
+
_, max_value, _ = algorithms.fft_submatrix_max(matrix)
|
| 24 |
+
assert max_value == 4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
def test_kidane():
|
| 27 |
+
# Test a 3x3 matrix.
|
| 28 |
+
matrix = np.array([
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
[1, -2, 3],
|
| 30 |
[-4, 5, -6],
|
| 31 |
[7, -8, 9]
|
| 32 |
])
|
| 33 |
+
# The best submatrix is expected to have a sum of 9.
|
| 34 |
+
_, max_value, _ = algorithms.kidane_max_submatrix(matrix)
|
| 35 |
+
assert max_value == 9
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|