Існує багато способів підходу до цієї проблеми. Растровий формат даних передбачає растровий підхід; при перегляді цих підходів формулювання проблеми як бінарної цілочислової лінійної програми виглядає перспективною, оскільки це дуже відповідає духу багатьох аналізів вибору сайтів ГІС і може легко адаптуватися до них.
У цій рецептурі перераховуємо всі можливі положення та орієнтації наповнюючих полігонів (ів), які я буду називати "плитками". Пов’язана з кожною плиткою міра її «доброти». Мета полягає в тому, щоб знайти колекцію плит, що не перекриваються, загальна корисність якомога більша. Тут ми можемо сприйняти користь кожної плитки як площу, яку вона покриває. (У більш багатих даними та складних середовищах прийняття рішень ми можемо обчислювати доброту як сукупність властивостей комірок, що входять до кожної плитки, властивостей, можливо, пов'язаних із видимістю, близькістю до інших речей тощо).
Обмеження цієї проблеми полягають у тому, що жодна плитка в розчині не може перекриватися.
Це може бути оформлена трохи більше абстрактно, таким чином , сприяє ефективному обчисленню, шляхом перерахування осередків на полігоні повинні бути заповнені (в «область») 1, 2, ..., M . Будь-яке розміщення плитки може бути кодоване індикаторним вектором нулів та розмірів, дозволяючи тим, що відповідають клітинкам, покритим плиткою та нулями в інших місцях. У цьому кодуванні всю інформацію, необхідну про колекцію плиток, можна знайти, підсумовуючи їхні показники векторів (компонент за складовою, як зазвичай): сума буде ненульовою саме там, де принаймні одна плитка покриває клітинку, і сума буде більшою ніж десь дві або більше плиток перекриваються. (Сума фактично підраховує суму перекриття.)
Ще одна маленька абстракція: безліч можливих місць розміщення плитки саме можна перерахувати, скажімо , 1, 2, ..., N . Вибір будь-якого набору розміщення плиток сам відповідає індикаторному вектору, де ті позначають плитку для розміщення.
Ось крихітна ілюстрація, щоб виправити ідеї . Він супроводжується кодом Mathematica, який використовується для розрахунків, так що труднощі програмування (або їх відсутність) можуть бути очевидними.
По-перше, ми зобразимо область, яку потрібно підкласти черепицею:
region = {{0, 0, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}};
Якщо пронумерувати його клітинки зліва направо, починаючи з верху, індикаторний вектор для регіону має 16 записів:
Flatten[region]
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
Давайте використовувати наступну плитку разом з усіма обертаннями кратними 90 градусам:
tileSet = {{{1, 1}, {1, 0}}};
Код для генерації обертів (і відображень):
apply[s_List, alpha] := Reverse /@ s;
apply[s_List, beta] := Transpose[s];
apply[s_List, g_List] := Fold[apply, s, g];
group = FoldList[Append, {}, Riffle[ConstantArray[alpha, 4], beta]];
tiles = Union[Flatten[Outer[apply[#1, #2] &, tileSet, group, 1], 1]];
(Цей дещо непрозорий обчислення пояснюється у відповіді за адресою /math//a/159159 , де показано, що він просто створює всі можливі повороти та відображення плитки, а потім видаляє всі повторювані результати.)
Припустимо, ми повинні розмістити плитку, як показано тут:
Клітини 3, 6 і 7 охоплені в цьому місці. Це позначається індикаторним вектором
{0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}
Якщо перенести цю плитку на один стовпчик праворуч, то цей індикаторний вектор був би
{0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}
Поєднання спроб розмістити плитку в обох цих положеннях одночасно визначається сумою цих показників,
{0, 0, 1, 1, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0}
2 у сьомому положенні показує ці перекриття в одній комірці (другий ряд вниз, третій стовпець зліва). Оскільки ми не хочемо перекриватись, ми вимагатимемо, щоб сума векторів у будь-якому дійсному рішенні не повинна мати записів, що перевищують 1.
Виявляється, для цієї проблеми для плиток можливі 29 комбінацій орієнтації та положення. (Це було виявлено за допомогою простого біта кодування, що включає вичерпний пошук.) Ми можемо зобразити всі 29 можливостей, намалювавши їх показники як вектори стовпців . (Використання стовпців замість рядків є звичайним.) Ось зображення отриманого масиву, який матиме 16 рядків (по одному для кожної можливої комірки у прямокутнику) та 29 стовпців:
makeAllTiles[tile_, {n_Integer, m_Integer}] :=
With[{ m0 = Length[tile], n0 = Length[First[tile]]},
Flatten[
Table[ArrayPad[tile, {{i, m - m0 - i}, {j, n - n0 - j}}], {i, 0, m - m0}, {j, 0, n - n0}], 1]];
allTiles = Flatten[ParallelMap[makeAllTiles[#, ImageDimensions[regionImage]] & , tiles], 1];
allTiles = Parallelize[
Select[allTiles, (regionVector . Flatten[#]) >= (Plus @@ (Flatten[#])) &]];
options = Transpose[Flatten /@ allTiles];
(Попередні два індикаторні вектори виглядають як перші два стовпчики зліва.) Гострозубий читач, можливо, помітив кілька можливостей для паралельної обробки: ці обчислення можуть зайняти кілька секунд.
Все вищесказане можна компактно перезапустити за допомогою матричних позначень:
F - це масив параметрів з M рядками та N стовпцями.
Х є індикатором набору плитки розміщень, довжини N .
b - N- вектор одиниць.
R - показник для регіону; це М- вектор.
Загальна «доброта», пов'язана з будь-яким можливим рішенням X , дорівнює RFX , оскільки FX - це показник клітин, охоплених X, а продукт з R підсумовує ці значення. (Ми могли б зважити R, якби побажали, щоб рішення сприяли або уникали певних районів регіону.) Це потрібно максимально використовувати. Тому що ми можемо записати це як ( РФ ). X , це лінійна функція X : це важливо. (У наведеному нижче коді змінна c
містить РФ .)
Ці обмеження є , що
Усі елементи X повинні бути негативними;
Усі елементи X повинні бути меншими за 1 (що є відповідним записом у b );
Усі елементи X повинні бути цілісними.
Обмеження (1) та (2) роблять цю програму лінійною , тоді як третя вимога перетворює її на цілу лінійну програму.
Існує багато пакетів для вирішення цілих лінійних програм, виражених саме в цій формі. Вони здатні обробляти значення M і N в десятки, а то й сотні тисяч. Це, мабуть, досить добре для деяких реальних програм.
Як наша перша ілюстрація, я обчислював рішення для попереднього прикладу, використовуючи команду Mathematica 8 LinearProgramming
. (Це дозволить мінімізувати лінійну цільову функцію. Мінімізація легко перетворюється на максимізацію шляхом відмови від цільової функції.) Він повернув рішення (як список плиток та їх позицій) за 0,011 секунд:
b = ConstantArray[-1, Length[options]];
c = -Flatten[region].options;
lu = ConstantArray[{0, 1}, Length[First[options]]];
x = LinearProgramming[c, -options, b, lu, Integers, Tolerance -> 0.05];
If[! ListQ[x] || Max[options.x] > 1, x = {}];
solution = allTiles[[Select[x Range[Length[x]], # > 0 &]]];
Сірі клітини взагалі відсутні в регіоні; білі клітини не були покриті цим розчином.
Ви можете розробити (вручну) безліч інших накладок, які так само хороші, як і цей, але кращого не знайти. Це потенційне обмеження такого підходу: він дає вам одне найкраще рішення, навіть коли їх існує більше. (Є деякі вирішення: якщо ми переупорядкуємо стовпці X , проблема залишається незмінною, але програмне забезпечення часто вибирає інше рішення як результат. Однак така поведінка непередбачувана.)
В якості другої ілюстрації , щоб бути більш реалістичною, розглянемо регіон у питанні. Імпортуючи зображення та перекомпонувавши його, я представив його сіткою 69 на 81:
Регіон включає 2156 осередків цієї сітки.
Щоб зробити речі цікавими та проілюструвати загальність установки лінійного програмування, спробуємо охопити якомога більше цієї області двома видами прямокутників:
Один - 17 на 9 (153 клітини), а другий - 15 на 11 (165 клітин). Ми можемо вважати за краще використовувати другий, тому що він більший, але перший є більш худим і може вміститися в тісніших місцях. Побачимо!
Зараз програма передбачає N = 5589 можливих розміщення плитки. Він досить великий! Після 6,3 секунди обчислення, Mathematica придумала це рішення з десяти плиток:
З - за деяку слабину ( .eg, ми могли б зрушити ліву нижню плитку до чотирьох стовпців зліва від нього ), є , очевидно , деякі інші рішення , що трохи відрізняються від цього.