Esistono molti modi per affrontare questo problema. Il formato raster dei dati suggerisce un approccio basato su raster; nel rivedere questi approcci, una formulazione del problema come programma lineare intero binario sembra promettente, perché è molto nello spirito di molte analisi di selezione del sito GIS e può essere prontamente adattata a loro.
In questa formulazione, enumeriamo tutte le possibili posizioni e orientamenti dei poligoni di riempimento, che chiamerò "tessere". Associato a ciascuna piastrella è una misura della sua "bontà". L'obiettivo è quello di trovare una collezione di piastrelle non sovrapposte la cui bontà totale sia il più grande possibile. Qui, possiamo prendere la bontà di ogni tessera per essere l'area che copre. (In ambienti decisionali più ricchi di dati e sofisticati, potremmo calcolare la bontà come una combinazione di proprietà delle celle incluse in ogni riquadro, proprietà forse legate alla visibilità, alla vicinanza ad altre cose e così via.)
I vincoli a questo problema sono semplicemente che non possono sovrapporsi due riquadri all'interno di una soluzione.
Questo può essere inquadrata un po 'più astrattamente, in modo favorevole al calcolo efficiente, elencando le cellule del poligono da riempire ( "regione") 1, 2, ..., M . Qualsiasi posizionamento delle tessere può essere codificato con un vettore indicatore di zeri e di uno, consentendo a quelli di corrispondere alle celle coperte dalla tessera e di zeri altrove. In questa codifica, tutte le informazioni necessarie su una raccolta di tessere possono essere trovate sommando i loro vettori indicatore (componente per componente, come al solito): la somma sarà diversa da zero esattamente dove almeno una tessera copre una cella e la somma sarà maggiore più di una dovunque due o più tessere si sovrappongono. (La somma conta efficacemente la quantità di sovrapposizione.)
Un altro po 'di astrazione: l'insieme dei possibili tirocini piastrelle può essa stessa essere enumerato, diciamo 1, 2, ..., N . La selezione di qualsiasi serie di posizionamenti di tessere stessa corrisponde a un vettore indicatore in cui quelli designano le tessere da posizionare.
Ecco una piccola illustrazione per correggere le idee . È accompagnato dal codice Mathematica utilizzato per eseguire i calcoli, in modo che la difficoltà di programmazione (o la sua mancanza) possa essere evidente.
Innanzitutto, descriviamo una regione da piastrellare:
region = {{0, 0, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}};
Se numeriamo le sue celle da sinistra a destra, iniziando dall'alto, il vettore indicatore per la regione ha 16 voci:
Flatten[region]
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
Usiamo la seguente tessera, insieme a tutte le rotazioni per multipli di 90 gradi:
tileSet = {{{1, 1}, {1, 0}}};
Codice per generare rotazioni (e riflessioni):
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]];
(Questo calcolo un po 'opaco è spiegato in una risposta su /math//a/159159 , che mostra che produce semplicemente tutte le possibili rotazioni e riflessioni di una piastrella e quindi rimuove qualsiasi risultato duplicato.)
Supponiamo di posizionare la tessera come mostrato qui:
Le celle 3, 6 e 7 sono coperte in questo posizionamento. Questo è indicato dal vettore indicatore
{0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}
Se spostiamo questa piastrella di una colonna a destra, sarebbe invece quel vettore indicatore
{0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}
La combinazione del tentativo di posizionare contemporaneamente le tessere in entrambe queste posizioni è determinata dalla somma di questi indicatori,
{0, 0, 1, 1, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0}
Il 2 nella settima posizione mostra queste sovrapposizioni in una cella (seconda riga in basso, terza colonna da sinistra). Poiché non vogliamo sovrapposizioni, richiederemo che la somma dei vettori in qualsiasi soluzione valida non abbia voci superiori a 1.
Si scopre che per questo problema sono possibili 29 combinazioni di orientamento e posizione per le piastrelle. (Questo è stato trovato con una semplice codifica che implica una ricerca esaustiva.) Possiamo rappresentare tutte le 29 possibilità disegnando i loro indicatori come vettori di colonna . (L'uso delle colonne anziché delle righe è convenzionale.) Ecco un'immagine dell'array risultante, che avrà 16 righe (una per ogni cella possibile nel rettangolo) e 29 colonne:
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];
(I due vettori indicatore precedenti appaiono come le prime due colonne a sinistra.) Il lettore dagli occhi acuti potrebbe aver notato diverse opportunità per l'elaborazione parallela: questi calcoli possono richiedere alcuni secondi.
Tutto quanto sopra può essere riformulato in modo compatto usando la notazione matriciale:
F è questo array di opzioni, con M righe e N colonne.
X è l'indicatore di una serie di posizionamenti piastrelle, di lunghezza N .
b è un vettore N di quelli.
R è l'indicatore per la regione; è un vettore M.
La "bontà" totale associata a qualsiasi possibile soluzione X è uguale a RFX , poiché FX è l'indicatore delle celle coperte da X e il prodotto con R somma questi valori. (Potremmo ponderare R se desiderassimo che le soluzioni favorissero o evitassero determinate aree della regione.) Questo deve essere massimizzato. Perché possiamo scriverlo come ( RF ). X , è una funzione lineare di X : questo è importante. (Nel codice seguente, la variabile c
contiene RF .)
I vincoli sono quelli
Tutti gli elementi di X devono essere non negativi;
Tutti gli elementi di X devono essere inferiori a 1 (che è la voce corrispondente in b );
Tutti gli elementi di X devono essere integrali.
I vincoli (1) e (2) rendono questo un programma lineare , mentre il terzo requisito lo trasforma in un programma lineare intero .
Esistono molti pacchetti per risolvere programmi lineari interi espressi esattamente in questa forma. Sono in grado di gestire i valori di M e N in decine o addirittura centinaia di migliaia. Questo è probabilmente abbastanza buono per alcune applicazioni del mondo reale.
Come prima illustrazione, ho calcolato una soluzione per l'esempio precedente usando il comando di Mathematica 8 LinearProgramming
. (Ciò minimizzerà una funzione oggettiva lineare. La minimizzazione si trasforma facilmente in massimizzazione negando la funzione obiettivo.) Restituiva una soluzione (come un elenco di tessere e le loro posizioni) in 0,011 secondi:
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 &]]];
Le celle grigie non si trovano affatto nella regione; i globuli bianchi non erano coperti da questa soluzione.
Puoi elaborare (a mano) molti altri limiti uguali a questo, ma non riesci a trovarne di migliori. Questo è un potenziale limitazione di questo approccio: ti dà una soluzione migliore, anche quando non v'è più di uno. (Esistono alcune soluzioni alternative: se riordiniamo le colonne di X , il problema rimane invariato, ma il software spesso sceglie una soluzione diversa di conseguenza. Tuttavia, questo comportamento è imprevedibile.)
Come seconda illustrazione , per essere più realistici, consideriamo la regione nella domanda. Importando l'immagine e ricampionandola, l'ho rappresentata con una griglia 69 per 81:
La regione comprende 2156 celle di questa griglia.
Per rendere le cose interessanti e illustrare la generalità della configurazione della programmazione lineare, proviamo a coprire il più possibile di questa regione con due tipi di rettangoli:
Uno è 17 per 9 (153 cellule) e l'altro è 15 per 11 (165 cellule). Potremmo preferire utilizzare il secondo, perché è più grande, ma il primo è più magro e può adattarsi in luoghi più stretti. Vediamo!
Il programma ora prevede N = 5589 possibili posizionamenti delle tessere. È abbastanza grande! Dopo 6,3 secondi di calcolo, Mathematica ha trovato questa soluzione a dieci tessere:
A causa di un po 'di allentamento ( .eg, potremmo spostare la piastrella in basso a sinistra fino a quattro colonne alla sua sinistra), ci sono ovviamente alcune altre soluzioni che differiscono leggermente da questa.