Licença

This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.

License: CC BY-SA 4.0

Citação

Sugestão de citação: FIGUEIREDO, Adriano Marcos Rodrigues. Economia Regional: exemplo de localização Comper. Campo Grande-MS,Brasil: RStudio/Rpubs, 2021. Disponível em http://rpubs.com/amrofi/comper_localization_weber.

1 Introdução

Os primeiros passos são criar ou abrir um diretório de trabalho. Se optar por criar um novo projeto, haverá a possibilidade de criar em uma pasta vazia. Em seguida, sugere-se que coloque os dados nesta pasta, se possível em um arquivo MS Excel e chame a planilha de ‘dados’.Neste caso, a planilha de dados será colocada dentro do código gerado pela função dput(). Os dados básicos foram extraídos de modo parcimonioso, para fins de exercício, a partir do Google Earth (manualmente) e convertido do sistema GMS para o sistema GD pela calculadora do INPE: http://www.dpi.inpe.br/calcula/. O código original veio de notas de aulas de VITON (2014).

2 Dados das coordenadas

Aqui chamaremos os dados embedded.

dados <- structure(list(local = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J"), 
    scoord = c(-20.4797, -20.4524833333, -20.4916583333, -20.4793916667, -20.4547444444, 
        -20.5157111111, -20.4771222222, -20.4359805556, -20.4656388889, -20.5159), 
    tcoord = c(-54.6105055556, -54.5938805556, -54.6454638889, -54.6407638889, -54.6345361111, 
        -54.6347361111, -54.59175, -54.6048861111, -54.6041, -54.65735), theta = c(1, 
        2, 1, 2, 2, 1, 2, 2, 1, 2), kcost = c(100, 100, 100, 100, 100, 100, 100, 
        100, 100, 100)), row.names = c(NA, -10L), class = c("tbl_df", "tbl", "data.frame"))

O arquivo básico contém 5 variáveis e 10 observações, a saber:

knitr::kable(dados)
local scoord tcoord theta kcost
A -20.47970 -54.61051 1 100
B -20.45248 -54.59388 2 100
C -20.49166 -54.64546 1 100
D -20.47939 -54.64076 2 100
E -20.45474 -54.63454 2 100
F -20.51571 -54.63474 1 100
G -20.47712 -54.59175 2 100
H -20.43598 -54.60489 2 100
I -20.46564 -54.60410 1 100
J -20.51590 -54.65735 2 100

em que: local são as lojas identificadas por letras; scoord e tcoord são, respectivamente, latitude e longitude extraídas do Google Earth e convertidas pela calculdadora online do INPE; theta e kcost são as variáveis do modelo, para o peso theta e o custo de transporte associado (shipping rate) \(k\) em unidades monetárias por km e por tonelada. A função a ser minimizada será o custo total de transporte do tipo:

\[ TTC=(k_1.θ_1.d(z_1,f))+(k_2.θ_2.d(z_2,f))+...+(k_n.θ_n.d(z_n,f)) \]

Os thetas são requisitos de insumos (em tons) para produzir 1 ton do produto final n cujo \(θ_n=1\). A medida d é a distância métrica, e \(d(a, b)\) é a distância entre os pontos a e b. Em um cenário padrão, todos os \(k\) seriam iguais.

Neste exemplo, para os dados acima dispostos, a primeira observação é o produto (mercado) em A. A otimização fornecerá o ponto central que minimiza os custos entre estes locais.

O modelo considerará a função distância edist, para uma matriz de coordenadas, e uma função para o custo - cost.

# NAO MEXER NAS FUNCOES
edist <- function(x, cmx) {
    # cmx is an nx2 coordinate matrix
    mx <- matrix(rep(x, each = nrow(cmx)), nrow = nrow(cmx))
    res <- sqrt(rowSums((cmx - mx)^2))
    res
}

cost <- function(x, data) {
    d <- edist(x, data[, c("scoord", "tcoord")])
    c <- sum(d * data[, "theta"] * data[, "kcost"])
    c
}

A função weber2 fará a otimização a partir de um ponto generico de coordenadas (0,0) (o leitor pode alterar se desejar, chamando para um ponto x=c(a,b) inicial). A função wplot fará um plot da solução.

weber2 <- function(start = c(0, 0), data = data) {
    z <- optim(start, cost, gr = NULL, data)
    weber_res <<- z  # save result as a global
    wplot(data, z)
    cat("\nProblem setting (locations can be inputs or final output):\n")
    for (i in 1:nrow(data)) {
        pr <- sprintf("Location %i : (%5.3f,%5.3f)\n   unit requirement %4.2f (tons); trans cost: %5.3f (/ton-mi)", 
            i, data[i, "scoord"], data[i, "tcoord"], data[i, "theta"], data[i, "kcost"])
        cat(pr, "\n")
    }
    # format = f avoids E notation for tiny values, which can arise for problems on a
    # line
    cat("\nSolution (see plot window):\nFactory located at: ", formatC(z$par, digits = 4, 
        format = "f"), "\n")
    cat("Total trans cost: ", formatC(z$value, digits = 5), "\n  ")
}
wplot <- function(data, res = NULL) {
    m <- data[, c("scoord", "tcoord")]
    m <- rbind(m, m[1, ])
    plot(m, type = "o")
    # in the next line, pch=19 is a filled circle
    if (!is.null(res)) {
        points(t(res$par), pch = 19)
    }
}

3 Execução

3.1 Com pesos diferentes

Neste cenário, nossos dados são como obtidos na seção anterior, com theta variando 1 e 2 os diferentes pontos.

weber2(start = c(0, 0), data = dados)


Problem setting (locations can be inputs or final output):
Location 1 : (-20.480,-54.611)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 2 : (-20.452,-54.594)
   unit requirement 2.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 3 : (-20.492,-54.645)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 4 : (-20.479,-54.641)
   unit requirement 2.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 5 : (-20.455,-54.635)
   unit requirement 2.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 6 : (-20.516,-54.635)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 7 : (-20.477,-54.592)
   unit requirement 2.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 8 : (-20.436,-54.605)
   unit requirement 2.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 9 : (-20.466,-54.604)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 10 : (-20.516,-54.657)
   unit requirement 2.00 (tons); trans cost: 100.000 (/ton-mi) 

Solution (see plot window):
Factory located at:  -20.4711 -54.6177 
Total trans cost:  51.229 
  

A solução é portanto o par para latitude -20.4711402 e longitude -54.6177371.

Vamos agora plotar no mapa. Primeiro extrairemos um mapa com os pontos dos mercados.

attach(dados)
# hiper compers
require(RgoogleMaps)
mt = GetMapTiles(latR = scoord, lonR = tcoord, verbose = 1, zoom = 10)
tileserver: http://mt1.google.com/vt/lyrs=m 
nTiles= 1 1 , center= -20.477 -54.622 
1 tiles to download 
tile image format: .png 
NOT downloading existing file 10_356_571.png
sleptTotal= 0 
PlotOnMapTiles(mt, lat = scoord, lon = tcoord, pch = 20, col = c("red", "blue", "green"), 
    cex = 2)

Depois ilustramos como alterar a cor dos pontos.

PlotOnMapTiles(mt, lat = scoord, lon = tcoord, pch = 10, col = c("red"), cex = 2)

# resultado weber_res$par[1] [1] -20.47114 weber_res$par[2] [1] -54.61774

latw = c(weber_res$par[1])
lonw = c(weber_res$par[2])
local_final = rbind(dados$local, "CT")
places = c("HComper", "HComper", "HComper", "HComper", "HComper", "HComper", "HComper", 
    "HComper", "HComper", "HComper", "CT")
mt = GetMapTiles(latR = scoord, lonR = tcoord, verbose = 1, zoom = 12)
tileserver: http://mt1.google.com/vt/lyrs=m 
nTiles= 1 2 , center= -20.477 -54.622 
2 tiles to download 
tile image format: .png 
NOT downloading existing file 12_1426_2285.png
NOT downloading existing file 12_1426_2286.png
sleptTotal= 0 

Por último, colocamos os pontos dos mercados junto com o do CT encontrado.

PlotOnMapTiles(mt, lat = c(scoord, latw), lon = c(tcoord, lonw), pch = c(20, 20, 
    20, 20, 20, 20, 20, 20, 20, 20, 10), col = c("red", "red", "red", "red", "red", 
    "red", "red", "red", "red", "red", "blue"), cex = 3)

3.2 Colocando theta =1 para todos

# colocando theta =1 para todos
dados1 <- structure(list(local = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J"), 
    scoord = c(-20.4797, -20.4524833333, -20.4916583333, -20.4793916667, -20.4547444444, 
        -20.5157111111, -20.4771222222, -20.4359805556, -20.4656388889, -20.5159), 
    tcoord = c(-54.6105055556, -54.5938805556, -54.6454638889, -54.6407638889, -54.6345361111, 
        -54.6347361111, -54.59175, -54.6048861111, -54.6041, -54.65735), theta = c(1, 
        1, 1, 1, 1, 1, 1, 1, 1, 1), kcost = c(100, 100, 100, 100, 100, 100, 100, 
        100, 100, 100)), row.names = c(NA, -10L), class = c("tbl_df", "tbl", "data.frame"))

Agora os dados tem theta =1 para todos:

knitr::kable(dados1)
local scoord tcoord theta kcost
A -20.47970 -54.61051 1 100
B -20.45248 -54.59388 1 100
C -20.49166 -54.64546 1 100
D -20.47939 -54.64076 1 100
E -20.45474 -54.63454 1 100
F -20.51571 -54.63474 1 100
G -20.47712 -54.59175 1 100
H -20.43598 -54.60489 1 100
I -20.46564 -54.60410 1 100
J -20.51590 -54.65735 1 100
weber2(start = c(0, 0), data = dados1)


Problem setting (locations can be inputs or final output):
Location 1 : (-20.480,-54.611)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 2 : (-20.452,-54.594)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 3 : (-20.492,-54.645)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 4 : (-20.479,-54.641)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 5 : (-20.455,-54.635)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 6 : (-20.516,-54.635)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 7 : (-20.477,-54.592)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 8 : (-20.436,-54.605)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 9 : (-20.466,-54.604)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 
Location 10 : (-20.516,-54.657)
   unit requirement 1.00 (tons); trans cost: 100.000 (/ton-mi) 

Solution (see plot window):
Factory located at:  -20.4749 -54.6167 
Total trans cost:  30.822 
  

Agora os resultados mudaram para o CT na latitude -20.4748517 e longitude -54.6167492.
Vamos agora plotar no mapa. Primeiro extrairemos um mapa com os pontos dos mercados. Depois ilustramos como alterar a cor dos pontos. Por último, colocamos os pontos dos mercados junto com o do CT encontrado.

attach(dados1)
# hiper compers
require(RgoogleMaps)
mt = GetMapTiles(latR = scoord, lonR = tcoord, verbose = 1, zoom = 10)
tileserver: http://mt1.google.com/vt/lyrs=m 
nTiles= 1 1 , center= -20.477 -54.622 
1 tiles to download 
tile image format: .png 
NOT downloading existing file 10_356_571.png
sleptTotal= 0 
PlotOnMapTiles(mt, lat = scoord, lon = tcoord, pch = 20, col = c("red", "blue", "green"), 
    cex = 2)

PlotOnMapTiles(mt, lat = scoord, lon = tcoord, pch = 10, col = c("red"), cex = 2)

# resultado -20.4749 -54.6167
latw = c(weber_res$par[1])
lonw = c(weber_res$par[2])
local_final = rbind(dados1$local, "CT")
places = c("HComper", "HComper", "HComper", "HComper", "HComper", "HComper", "HComper", 
    "HComper", "HComper", "HComper", "CT")
mt = GetMapTiles(latR = scoord, lonR = tcoord, verbose = 1, zoom = 12)
tileserver: http://mt1.google.com/vt/lyrs=m 
nTiles= 1 2 , center= -20.477 -54.622 
2 tiles to download 
tile image format: .png 
NOT downloading existing file 12_1426_2285.png
NOT downloading existing file 12_1426_2286.png
sleptTotal= 0 
PlotOnMapTiles(mt, lat = c(scoord, latw), lon = c(tcoord, lonw), pch = c(20, 20, 
    20, 20, 20, 20, 20, 20, 20, 20, 10), col = c("red", "red", "red", "red", "red", 
    "red", "red", "red", "red", "red", "blue"), cex = 3)

4 Usar Google maps para melhor visualização do CT

Neste caso, usaremos o resultado do primeiro exemplo, com pesos diferentes, e procuramos nas coordenadas c(-20.47114,-54.61774) no Google Maps.

Localizacao do CT - Cenario 1. Fonte: Elaboração própria com <https://www.google.com.br/maps/>.

Referências

VITON, Philip A. Using R to Solve Weber Problems. (CRPLAN 6600 slides) Ohio State University, 2014.

LS0tDQp0aXRsZTogIkVjb25vbWlhIFJlZ2lvbmFsOiBleGVtcGxvIGRlIGxvY2FsaXphw6fDo28gQ29tcGVyIg0KYXV0aG9yOiAiQWRyaWFubyBNYXJjb3MgUm9kcmlndWVzIEZpZ3VlaXJlZG8iDQplLW1haWw6ICJhZHJpYW5vLmZpZ3VlaXJlZG9AdWZtcy5iciINCmFic3RyYWN0OiAiV2UgYW5hbHlzZSBhIHNpbXBsZSBsb2NhbGl6YXRpb24gcHJvYmxlbS4gQ2xhc3MgZXhlcmNpc2UiIA0KZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpLCAnJWQgJUIgJVknKWAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiBkZWZhdWx0DQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IG5vDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgZmlnX2NhcHRpb246IHRydWUNCi0tLQ0KDQpgYGB7ciBrbml0cl9pbml0LCBlY2hvPUZBTFNFLCBjYWNoZT1GQUxTRX0NCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KHJtYXJrZG93bikNCmxpYnJhcnkocm1kZm9ybWF0cykNCg0KIyMgR2xvYmFsIG9wdGlvbnMNCm9wdGlvbnMobWF4LnByaW50PSIxMDAiKQ0Kb3B0c19jaHVuayRzZXQoZWNobz1UUlVFLA0KCSAgICAgICAgICAgICBjYWNoZT1UUlVFLA0KICAgICAgICAgICAgICAgcHJvbXB0PUZBTFNFLA0KICAgICAgICAgICAgICAgdGlkeT1UUlVFLA0KICAgICAgICAgICAgICAgY29tbWVudD1OQSwNCiAgICAgICAgICAgICAgIG1lc3NhZ2U9RkFMU0UsDQogICAgICAgICAgICAgICB3YXJuaW5nPUZBTFNFKQ0Kb3B0c19rbml0JHNldCh3aWR0aD0xMDApDQpgYGANCg0KIyBMaWNlbsOnYSB7I0xpY2Vuw6dhIC51bm51bWJlcmVkfQ0KDQpUaGlzIHdvcmsgaXMgbGljZW5zZWQgdW5kZXIgdGhlIENyZWF0aXZlIENvbW1vbnMgQXR0cmlidXRpb24tU2hhcmVBbGlrZSA0LjAgSW50ZXJuYXRpb25hbCBMaWNlbnNlLiBUbyB2aWV3IGEgY29weSBvZiB0aGlzIGxpY2Vuc2UsIHZpc2l0IDxodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1zYS80LjAvPiBvciBzZW5kIGEgbGV0dGVyIHRvIENyZWF0aXZlIENvbW1vbnMsIFBPIEJveCAxODY2LCBNb3VudGFpbiBWaWV3LCBDQSA5NDA0MiwgVVNBLg0KDQohW0xpY2Vuc2U6IENDIEJZLVNBIDQuMF0oaHR0cHM6Ly9taXJyb3JzLmNyZWF0aXZlY29tbW9ucy5vcmcvcHJlc3NraXQvYnV0dG9ucy84OHgzMS9wbmcvYnktc2EucG5nKXt3aWR0aD0iMjUlIn0NCg0KIyBDaXRhw6fDo28geyNDaXRhw6fDo28gLnVubnVtYmVyZWR9DQoNClN1Z2VzdMOjbyBkZSBjaXRhw6fDo286IEZJR1VFSVJFRE8sIEFkcmlhbm8gTWFyY29zIFJvZHJpZ3Vlcy4gRWNvbm9taWEgUmVnaW9uYWw6IGV4ZW1wbG8gZGUgbG9jYWxpemHDp8OjbyBDb21wZXIuIENhbXBvIEdyYW5kZS1NUyxCcmFzaWw6IFJTdHVkaW8vUnB1YnMsIDIwMjEuIERpc3BvbsOtdmVsIGVtIDxodHRwOi8vcnB1YnMuY29tL2Ftcm9maS9jb21wZXJfbG9jYWxpemF0aW9uX3dlYmVyPi4NCg0KIyBJbnRyb2R1w6fDo28NCg0KT3MgcHJpbWVpcm9zIHBhc3NvcyBzw6NvIGNyaWFyIG91IGFicmlyIHVtIGRpcmV0w7NyaW8gZGUgdHJhYmFsaG8uIFNlIG9wdGFyIHBvciBjcmlhciB1bSBub3ZvIHByb2pldG8sIGhhdmVyw6EgYSBwb3NzaWJpbGlkYWRlIGRlIGNyaWFyIGVtIHVtYSBwYXN0YSB2YXppYS4gRW0gc2VndWlkYSwgc3VnZXJlLXNlIHF1ZSBjb2xvcXVlIG9zIGRhZG9zIG5lc3RhIHBhc3RhLCBzZSBwb3Nzw612ZWwgZW0gdW0gYXJxdWl2byBNUyBFeGNlbCBlIGNoYW1lIGEgcGxhbmlsaGEgZGUgJ2RhZG9zJy5OZXN0ZSBjYXNvLCBhIHBsYW5pbGhhIGRlIGRhZG9zIHNlcsOhIGNvbG9jYWRhIGRlbnRybyBkbyBjw7NkaWdvIGdlcmFkbyBwZWxhIGZ1bsOnw6NvIGBkcHV0KClgLiBPcyBkYWRvcyBiw6FzaWNvcyBmb3JhbSBleHRyYcOtZG9zIGRlIG1vZG8gcGFyY2ltb25pb3NvLCBwYXJhIGZpbnMgZGUgZXhlcmPDrWNpbywgYSBwYXJ0aXIgZG8gR29vZ2xlIEVhcnRoIChtYW51YWxtZW50ZSkgZSBjb252ZXJ0aWRvIGRvIHNpc3RlbWEgR01TIHBhcmEgbyBzaXN0ZW1hIEdEIHBlbGEgY2FsY3VsYWRvcmEgZG8gSU5QRTogPGh0dHA6Ly93d3cuZHBpLmlucGUuYnIvY2FsY3VsYS8+LiBPIGPDs2RpZ28gb3JpZ2luYWwgdmVpbyBkZSBub3RhcyBkZSBhdWxhcyBkZSBWSVRPTiAoMjAxNCkuDQoNCiMgRGFkb3MgZGFzIGNvb3JkZW5hZGFzDQoNCkFxdWkgY2hhbWFyZW1vcyBvcyBkYWRvcyBlbWJlZGRlZC4NCg0KYGBge3J9DQpkYWRvczwtDQogIHN0cnVjdHVyZShsaXN0KGxvY2FsID0gYygiQSIsICJCIiwgIkMiLCAiRCIsICJFIiwgIkYiLCAiRyIsICJIIiwgDQoiSSIsICJKIiksIHNjb29yZCA9IGMoLTIwLjQ3OTcsIC0yMC40NTI0ODMzMzMzLCAtMjAuNDkxNjU4MzMzMywgDQotMjAuNDc5MzkxNjY2NywgLTIwLjQ1NDc0NDQ0NDQsIC0yMC41MTU3MTExMTExLCAtMjAuNDc3MTIyMjIyMiwgDQotMjAuNDM1OTgwNTU1NiwgLTIwLjQ2NTYzODg4ODksIC0yMC41MTU5KSwgdGNvb3JkID0gYygtNTQuNjEwNTA1NTU1NiwgDQotNTQuNTkzODgwNTU1NiwgLTU0LjY0NTQ2Mzg4ODksIC01NC42NDA3NjM4ODg5LCAtNTQuNjM0NTM2MTExMSwgDQotNTQuNjM0NzM2MTExMSwgLTU0LjU5MTc1LCAtNTQuNjA0ODg2MTExMSwgLTU0LjYwNDEsIC01NC42NTczNQ0KKSwgdGhldGEgPSBjKDEsIDIsIDEsIDIsIDIsIDEsIDIsIDIsIDEsIDIpLCBrY29zdCA9IGMoMTAwLCAxMDAsIA0KMTAwLCAxMDAsIDEwMCwgMTAwLCAxMDAsIDEwMCwgMTAwLCAxMDApKSwgcm93Lm5hbWVzID0gYyhOQSwgLTEwTA0KKSwgY2xhc3MgPSBjKCJ0YmxfZGYiLCAidGJsIiwgImRhdGEuZnJhbWUiKSkNCmBgYA0KDQpPIGFycXVpdm8gYsOhc2ljbyBjb250w6ltIDUgdmFyacOhdmVpcyBlIDEwIG9ic2VydmHDp8O1ZXMsIGEgc2FiZXI6DQoNCmBgYHtyfQ0Ka25pdHI6OmthYmxlKGRhZG9zKQ0KYGBgDQoNCmVtIHF1ZTogYGxvY2FsYCBzw6NvIGFzIGxvamFzIGlkZW50aWZpY2FkYXMgcG9yIGxldHJhczsgYHNjb29yZGAgZSBgdGNvb3JkYCBzw6NvLCByZXNwZWN0aXZhbWVudGUsIGxhdGl0dWRlIGUgbG9uZ2l0dWRlIGV4dHJhw61kYXMgZG8gR29vZ2xlIEVhcnRoIGUgY29udmVydGlkYXMgcGVsYSBjYWxjdWxkYWRvcmEgb25saW5lIGRvIElOUEU7IGB0aGV0YWAgZSBga2Nvc3RgIHPDo28gYXMgdmFyacOhdmVpcyBkbyBtb2RlbG8sIHBhcmEgbyBwZXNvIHRoZXRhIGUgbyBjdXN0byBkZSB0cmFuc3BvcnRlIGFzc29jaWFkbyAoKnNoaXBwaW5nIHJhdGUqKSAkayQgZW0gdW5pZGFkZXMgbW9uZXTDoXJpYXMgcG9yIGttIGUgcG9yIHRvbmVsYWRhLiBBIGZ1bsOnw6NvIGEgc2VyIG1pbmltaXphZGEgc2Vyw6EgbyBjdXN0byB0b3RhbCBkZSB0cmFuc3BvcnRlIGRvIHRpcG86DQoNCiQkDQpUVEM9KGtfMS7OuF8xLmQoel8xLGYpKSsoa18yLs64XzIuZCh6XzIsZikpKy4uLisoa19uLs64X24uZCh6X24sZikpIA0KJCQNCg0KT3MgdGhldGFzIHPDo28gcmVxdWlzaXRvcyBkZSBpbnN1bW9zIChlbSB0b25zKSBwYXJhIHByb2R1emlyIDEgdG9uIGRvIHByb2R1dG8gZmluYWwgbiBjdWpvICTOuF9uPTEkLiBBIG1lZGlkYSBkIMOpIGEgZGlzdMOibmNpYSBtw6l0cmljYSwgZSAkZChhLCBiKSQgw6kgYSBkaXN0w6JuY2lhIGVudHJlIG9zIHBvbnRvcyBhIGUgYi4gRW0gdW0gY2Vuw6FyaW8gcGFkcsOjbywgdG9kb3Mgb3MgJGskIHNlcmlhbSBpZ3VhaXMuDQoNCk5lc3RlIGV4ZW1wbG8sIHBhcmEgb3MgZGFkb3MgYWNpbWEgZGlzcG9zdG9zLCBhIHByaW1laXJhIG9ic2VydmHDp8OjbyDDqSBvIHByb2R1dG8gKG1lcmNhZG8pIGVtIEEuIEEgb3RpbWl6YcOnw6NvIGZvcm5lY2Vyw6EgbyBwb250byBjZW50cmFsIHF1ZSBtaW5pbWl6YSBvcyBjdXN0b3MgZW50cmUgZXN0ZXMgbG9jYWlzLg0KDQpPIG1vZGVsbyBjb25zaWRlcmFyw6EgYSBmdW7Dp8OjbyBkaXN0w6JuY2lhIGBlZGlzdGAsIHBhcmEgdW1hIG1hdHJpeiBkZSBjb29yZGVuYWRhcywgZSB1bWEgZnVuw6fDo28gcGFyYSBvIGN1c3RvIC0gYGNvc3RgLg0KDQpgYGB7cn0NCiMgTkFPIE1FWEVSIE5BUyBGVU5DT0VTDQplZGlzdDwtZnVuY3Rpb24oeCxjbXgpew0KICAjIGNteCBpcyBhbiBueDIgY29vcmRpbmF0ZSBtYXRyaXgNCiAgbXg8LW1hdHJpeChyZXAoeCwgZWFjaD1ucm93KGNteCkpLG5yb3c9bnJvdyhjbXgpKQ0KICByZXM8LXNxcnQocm93U3VtcygoY214LW14KV4yKSkNCiAgcmVzDQp9DQoNCmNvc3Q8LWZ1bmN0aW9uKHgsZGF0YSl7DQogIGQ8LWVkaXN0KHgsZGF0YVssYygic2Nvb3JkIiwidGNvb3JkIildKQ0KICBjPC1zdW0oZCpkYXRhWywidGhldGEiXSpkYXRhWywia2Nvc3QiXSkJDQogIGMNCn0NCmBgYA0KDQpBIGZ1bsOnw6NvIGB3ZWJlcjJgIGZhcsOhIGEgb3RpbWl6YcOnw6NvIGEgcGFydGlyIGRlIHVtIHBvbnRvIGdlbmVyaWNvIGRlIGNvb3JkZW5hZGFzICgwLDApIChvIGxlaXRvciBwb2RlIGFsdGVyYXIgc2UgZGVzZWphciwgY2hhbWFuZG8gcGFyYSB1bSBwb250byBgeD1jKGEsYilgIGluaWNpYWwpLiBBIGZ1bsOnw6NvIGB3cGxvdGAgZmFyw6EgdW0gcGxvdCBkYSBzb2x1w6fDo28uDQoNCmBgYHtyfQ0Kd2ViZXIyPC1mdW5jdGlvbihzdGFydD1jKDAsMCksZGF0YT1kYXRhKXsNCiAgejwtb3B0aW0oc3RhcnQsY29zdCxncj1OVUxMLGRhdGEpDQogIHdlYmVyX3Jlczw8LXogIyBzYXZlIHJlc3VsdCBhcyBhIGdsb2JhbA0KICB3cGxvdChkYXRhLHopDQogIGNhdCgiXG5Qcm9ibGVtIHNldHRpbmcgKGxvY2F0aW9ucyBjYW4gYmUgaW5wdXRzIG9yIGZpbmFsIG91dHB1dCk6XG4iKQ0KICBmb3IgKGkgaW4gMTpucm93KGRhdGEpKXsNCiAgICBwcjwtc3ByaW50ZigiTG9jYXRpb24gJWkgOiAoJTUuM2YsJTUuM2YpXG4gICB1bml0IHJlcXVpcmVtZW50ICU0LjJmICh0b25zKTsgdHJhbnMgY29zdDogJTUuM2YgKC90b24tbWkpIiwNCiAgICAgICAgICAgICAgICBpLGRhdGFbaSwic2Nvb3JkIl0sZGF0YVtpLCJ0Y29vcmQiXSxkYXRhW2ksInRoZXRhIl0sZGF0YVtpLCJrY29zdCJdKQ0KICAgIGNhdChwciwiXG4iKQ0KICB9CQ0KICAjIGZvcm1hdCA9IGYgYXZvaWRzIEUgbm90YXRpb24gZm9yIHRpbnkgdmFsdWVzLCB3aGljaCBjYW4gYXJpc2UgZm9yIHByb2JsZW1zIG9uIGEgbGluZQ0KICBjYXQoIlxuU29sdXRpb24gKHNlZSBwbG90IHdpbmRvdyk6XG5GYWN0b3J5IGxvY2F0ZWQgYXQ6ICIsZm9ybWF0Qyh6JHBhcixkaWdpdHM9NCxmb3JtYXQ9ImYiKSwiXG4iKQ0KICBjYXQoIlRvdGFsIHRyYW5zIGNvc3Q6ICIsZm9ybWF0Qyh6JHZhbHVlLGRpZ2l0cz01KSwiXG4gICIpCQ0KfQ0Kd3Bsb3Q8LWZ1bmN0aW9uKGRhdGEscmVzPU5VTEwpew0KICBtPC1kYXRhWyxjKCJzY29vcmQiLCJ0Y29vcmQiKV0NCiAgbTwtcmJpbmQobSxtWzEsXSkNCiAgcGxvdChtLHR5cGU9Im8iKQ0KICAjIGluIHRoZSBuZXh0IGxpbmUsIHBjaD0xOSBpcyBhIGZpbGxlZCBjaXJjbGUNCiAgaWYgKCFpcy5udWxsKHJlcykpe3BvaW50cyh0KHJlcyRwYXIpLHBjaD0xOSl9DQp9DQpgYGANCg0KIyBFeGVjdcOnw6NvDQoNCiMjIENvbSBwZXNvcyBkaWZlcmVudGVzDQoNCk5lc3RlIGNlbsOhcmlvLCBub3Nzb3MgZGFkb3Mgc8OjbyBjb21vIG9idGlkb3MgbmEgc2XDp8OjbyBhbnRlcmlvciwgY29tIHRoZXRhIHZhcmlhbmRvIDEgZSAyIG9zIGRpZmVyZW50ZXMgcG9udG9zLg0KDQpgYGB7cn0NCndlYmVyMihzdGFydD1jKDAsMCksZGF0YT1kYWRvcykNCmBgYA0KDQpBIHNvbHXDp8OjbyDDqSBwb3J0YW50byBvIHBhciBwYXJhIGxhdGl0dWRlIGByIHdlYmVyX3JlcyRwYXJbMV1gIGUgbG9uZ2l0dWRlIGByIHdlYmVyX3JlcyRwYXJbMl1gLiAgICANCg0KVmFtb3MgYWdvcmEgcGxvdGFyIG5vIG1hcGEuIFByaW1laXJvIGV4dHJhaXJlbW9zIHVtIG1hcGEgY29tIG9zIHBvbnRvcyBkb3MgbWVyY2Fkb3MuIA0KDQpgYGB7cn0NCmF0dGFjaChkYWRvcykNCiMgaGlwZXIgY29tcGVycw0KcmVxdWlyZShSZ29vZ2xlTWFwcykNCm10ID0gR2V0TWFwVGlsZXMobGF0UiA9c2Nvb3JkICwgbG9uUj10Y29vcmQsdmVyYm9zZT0xLHpvb20gPSAxMCkNClBsb3RPbk1hcFRpbGVzKG10LGxhdD1zY29vcmQsbG9uPXRjb29yZCxwY2g9MjAsDQogICAgICAgICAgICAgICBjb2w9YygncmVkJywgJ2JsdWUnLCAnZ3JlZW4nKSxjZXg9MikNCmBgYA0KICAgIA0KRGVwb2lzIGlsdXN0cmFtb3MgY29tbyBhbHRlcmFyIGEgY29yIGRvcyBwb250b3MuICAgIA0KDQpgYGB7cn0NClBsb3RPbk1hcFRpbGVzKG10LGxhdD1zY29vcmQsbG9uPXRjb29yZCxwY2g9MTAsDQogICAgICAgICAgICAgICBjb2w9YygncmVkJyksY2V4PTIpDQojIHJlc3VsdGFkbyANCiMgd2ViZXJfcmVzJHBhclsxXQ0KI1sxXSAtMjAuNDcxMTQNCiMgd2ViZXJfcmVzJHBhclsyXQ0KI1sxXSAtNTQuNjE3NzQNCg0KbGF0dyA9IGMod2ViZXJfcmVzJHBhclsxXSk7DQpsb253ID0gYyh3ZWJlcl9yZXMkcGFyWzJdKTsNCmxvY2FsX2ZpbmFsPXJiaW5kKGRhZG9zJGxvY2FsLCdDVCcpDQpwbGFjZXMgPSBjKCdIQ29tcGVyJywnSENvbXBlcicsJ0hDb21wZXInLCdIQ29tcGVyJywnSENvbXBlcicsDQogICAgICAgICAgICdIQ29tcGVyJywnSENvbXBlcicsJ0hDb21wZXInLCdIQ29tcGVyJywnSENvbXBlcicsJ0NUJykNCm10ID0gR2V0TWFwVGlsZXMobGF0UiA9c2Nvb3JkICwgbG9uUj10Y29vcmQsdmVyYm9zZT0xLHpvb20gPSAxMikNCmBgYA0KDQpQb3Igw7psdGltbywgY29sb2NhbW9zIG9zIHBvbnRvcyBkb3MgbWVyY2Fkb3MganVudG8gY29tIG8gZG8gQ1QgZW5jb250cmFkby4gICAgDQoNCmBgYHtyfQ0KUGxvdE9uTWFwVGlsZXMobXQsbGF0PWMoc2Nvb3JkLGxhdHcpLGxvbj1jKHRjb29yZCxsb253KSwNCiAgICAgICAgICAgICAgIHBjaD1jKDIwLDIwLDIwLDIwLDIwLDIwLDIwLDIwLDIwLDIwLDEwKSwNCiAgICAgICAgICAgICAgIGNvbD1jKCdyZWQnLCdyZWQnLCdyZWQnLCdyZWQnLCdyZWQnLA0KICAgICAgICAgICAgICAgICAgICAgJ3JlZCcsJ3JlZCcsJ3JlZCcsJ3JlZCcsJ3JlZCcsDQogICAgICAgICAgICAgICAgICAgICAnYmx1ZScpLGNleD0zKQ0KYGBgDQoNCiMjIENvbG9jYW5kbyB0aGV0YSA9MSBwYXJhIHRvZG9zDQoNCmBgYHtyfQ0KIyBjb2xvY2FuZG8gdGhldGEgPTEgcGFyYSB0b2Rvcw0KZGFkb3MxIDwtIA0KICBzdHJ1Y3R1cmUobGlzdChsb2NhbCA9IGMoIkEiLCAiQiIsICJDIiwgIkQiLCAiRSIsICJGIiwgIkciLCAiSCIsIA0KIkkiLCAiSiIpLCBzY29vcmQgPSBjKC0yMC40Nzk3LCAtMjAuNDUyNDgzMzMzMywgLTIwLjQ5MTY1ODMzMzMsIA0KLTIwLjQ3OTM5MTY2NjcsIC0yMC40NTQ3NDQ0NDQ0LCAtMjAuNTE1NzExMTExMSwgLTIwLjQ3NzEyMjIyMjIsIA0KLTIwLjQzNTk4MDU1NTYsIC0yMC40NjU2Mzg4ODg5LCAtMjAuNTE1OSksIHRjb29yZCA9IGMoLTU0LjYxMDUwNTU1NTYsIA0KLTU0LjU5Mzg4MDU1NTYsIC01NC42NDU0NjM4ODg5LCAtNTQuNjQwNzYzODg4OSwgLTU0LjYzNDUzNjExMTEsIA0KLTU0LjYzNDczNjExMTEsIC01NC41OTE3NSwgLTU0LjYwNDg4NjExMTEsIC01NC42MDQxLCAtNTQuNjU3MzUNCiksIHRoZXRhID0gYygxLCAxLCAxLCAxLCAxLCAxLCAxLCAxLCAxLCAxKSwga2Nvc3QgPSBjKDEwMCwgMTAwLCANCjEwMCwgMTAwLCAxMDAsIDEwMCwgMTAwLCAxMDAsIDEwMCwgMTAwKSksIHJvdy5uYW1lcyA9IGMoTkEsIC0xMEwNCiksIGNsYXNzID0gYygidGJsX2RmIiwgInRibCIsICJkYXRhLmZyYW1lIikpDQpgYGANCg0KQWdvcmEgb3MgZGFkb3MgdGVtIHRoZXRhID0xIHBhcmEgdG9kb3M6DQoNCmBgYHtyfQ0Ka25pdHI6OmthYmxlKGRhZG9zMSkNCmBgYA0KDQpgYGB7cn0NCndlYmVyMihzdGFydD1jKDAsMCksZGF0YT1kYWRvczEpDQpgYGANCg0KQWdvcmEgb3MgcmVzdWx0YWRvcyBtdWRhcmFtIHBhcmEgbyBDVCBuYSBsYXRpdHVkZSBgciB3ZWJlcl9yZXMkcGFyWzFdYCBlIGxvbmdpdHVkZSBgciB3ZWJlcl9yZXMkcGFyWzJdYC4gICAgDQpWYW1vcyBhZ29yYSBwbG90YXIgbm8gbWFwYS4gUHJpbWVpcm8gZXh0cmFpcmVtb3MgdW0gbWFwYSBjb20gb3MgcG9udG9zIGRvcyBtZXJjYWRvcy4gRGVwb2lzIGlsdXN0cmFtb3MgY29tbyBhbHRlcmFyIGEgY29yIGRvcyBwb250b3MuIFBvciDDumx0aW1vLCBjb2xvY2Ftb3Mgb3MgcG9udG9zIGRvcyBtZXJjYWRvcyBqdW50byBjb20gbyBkbyBDVCBlbmNvbnRyYWRvLg0KDQoNCmBgYHtyfQ0KYXR0YWNoKGRhZG9zMSkNCiMgaGlwZXIgY29tcGVycw0KcmVxdWlyZShSZ29vZ2xlTWFwcykNCm10ID0gR2V0TWFwVGlsZXMobGF0UiA9c2Nvb3JkICwgbG9uUj10Y29vcmQsdmVyYm9zZT0xLHpvb20gPSAxMCkNClBsb3RPbk1hcFRpbGVzKG10LGxhdD1zY29vcmQsbG9uPXRjb29yZCxwY2g9MjAsDQogICAgICAgICAgICAgICBjb2w9YygncmVkJywgJ2JsdWUnLCAnZ3JlZW4nKSxjZXg9MikNClBsb3RPbk1hcFRpbGVzKG10LGxhdD1zY29vcmQsbG9uPXRjb29yZCxwY2g9MTAsDQogICAgICAgICAgICAgICBjb2w9YygncmVkJyksY2V4PTIpDQojIHJlc3VsdGFkbyAtMjAuNDc0OSAtNTQuNjE2Nw0KbGF0dyA9IGMod2ViZXJfcmVzJHBhclsxXSk7DQpsb253ID0gYyh3ZWJlcl9yZXMkcGFyWzJdKTsNCmxvY2FsX2ZpbmFsPXJiaW5kKGRhZG9zMSRsb2NhbCwnQ1QnKQ0KcGxhY2VzID0gYygnSENvbXBlcicsJ0hDb21wZXInLCdIQ29tcGVyJywnSENvbXBlcicsJ0hDb21wZXInLA0KICAgICAgICAgICAnSENvbXBlcicsJ0hDb21wZXInLCdIQ29tcGVyJywnSENvbXBlcicsJ0hDb21wZXInLCdDVCcpDQptdCA9IEdldE1hcFRpbGVzKGxhdFIgPXNjb29yZCAsIGxvblI9dGNvb3JkLHZlcmJvc2U9MSx6b29tID0gMTIpDQpQbG90T25NYXBUaWxlcyhtdCxsYXQ9YyhzY29vcmQsbGF0dyksbG9uPWModGNvb3JkLGxvbncpLA0KICAgICAgICAgICAgICAgcGNoPWMoMjAsMjAsMjAsMjAsMjAsMjAsMjAsMjAsMjAsMjAsMTApLA0KICAgICAgICAgICAgICAgY29sPWMoJ3JlZCcsJ3JlZCcsJ3JlZCcsJ3JlZCcsJ3JlZCcsDQogICAgICAgICAgICAgICAgICAgICAncmVkJywncmVkJywncmVkJywncmVkJywncmVkJywNCiAgICAgICAgICAgICAgICAgICAgICdibHVlJyksY2V4PTMpDQpgYGANCg0KIyBVc2FyIEdvb2dsZSBtYXBzIHBhcmEgbWVsaG9yIHZpc3VhbGl6YcOnw6NvIGRvIENUDQoNCk5lc3RlIGNhc28sIHVzYXJlbW9zIG8gcmVzdWx0YWRvIGRvIHByaW1laXJvIGV4ZW1wbG8sIGNvbSBwZXNvcyBkaWZlcmVudGVzLCBlIHByb2N1cmFtb3MgbmFzIGNvb3JkZW5hZGFzIGMoLTIwLjQ3MTE0LC01NC42MTc3NCkgbm8gR29vZ2xlIE1hcHMuDQoNCiFbTG9jYWxpemFjYW8gZG8gQ1QgLSBDZW5hcmlvIDEuIEZvbnRlOiBFbGFib3Jhw6fDo28gcHLDs3ByaWEgY29tIFw8PGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20uYnIvbWFwcy8+XD4uXShtYXBhX0NUX0dvb2dsZW1hcHNfY2VuYXJpbzEucG5nICkNCg0KIyBSZWZlcsOqbmNpYXMgeyNSZWZlcsOqbmNpYXMgLnVubnVtYmVyZWR9DQoNClZJVE9OLCBQaGlsaXAgQS4gVXNpbmcgUiB0byBTb2x2ZSBXZWJlciBQcm9ibGVtcy4gKENSUExBTiA2NjAwIHNsaWRlcykgT2hpbyBTdGF0ZSBVbml2ZXJzaXR5LCAyMDE0Lg0K