Interfaz de R a Python usando Reticulate

El paquete reticulate proporciona una interfaz R para módulos, clases y funciones de Python.

https://rstudio.github.io/reticulate/

El primer paso es instalar Python a traves de por ejemplo Anaconda. https://www.anaconda.com/products/distribution

Luego instalar el paquete reticulate desde CRAN usando install.packages('reticulate')

Version de Python

De manera predeterminada, reticulate usa la versión de Python que se encuentra en el PATH Sys.which("python")

La función use_python() te permite especificar una versión alternativa, por ejemplo:

library(reticulate)

use_python("/usr/local/bin/python")

quit
library(reticulate)

Funciones básicas usando Python chunks

a = "Hola " + "Mundo"
print(a)
Hola Mundo

Ejemplo de una gráfica Python Matplotlib

import matplotlib.pyplot as plt
import numpy as np

# Data for plotting
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)

fig, ax = plt.subplots()
ax.plot(t, s)
[<matplotlib.lines.Line2D object at 0x000001F65ADF9CA0>]
ax.set(xlabel='time (s)', ylabel='voltage (mV)')
[Text(0.5, 0, 'time (s)'), Text(0, 0.5, 'voltage (mV)')]
ax.grid()

plt.show()

Acceder a Copernicus Copernicus Marine Service Catalogue

Si no tiene motuclient en Python, deberá instalarlo usando:

python -m pip install motuclient==1.8.4 --no-cache-dir

Deberás reemplazar tus credenciales de inicio de sesión de Copernicus en el código de abojo y correlo en un chuck de Python.

También puedes cambiar el nombre del archivo y el directorio de salida según sus necesidades.

import motuclient

!python -m motuclient --motu https://nrt.cmems-du.eu/motu-web/Motu --service-id MEDSEA_ANALYSISFORECAST_PHY_006_013-TDS --product-id med-cmcc-tem-an-fc-h --longitude-min -17.2917 --longitude-max 36.2917 --latitude-min 30.1875 --latitude-max 45.9792 --date-min "2022-06-18 23:30:00" --date-max "2022-06-18 23:30:00" --depth-min 1.0182 --depth-max 1.0183 --variable thetao --out-dir nc_files --out-name temp_18_june.nc --user jatalah --pwd ***********

Convertir fichero NetCDF a tibble

Primero se instala el paquete tidync usando:

install.packages("tidync")

Ver tutorial en este blog https://ropensci.org/blog/2019/11/05/tidync/

quit
library(tidync)

sst_june_22 <- tidync('nc_files/temp_18_june.nc')

sst_june_22

Data Source (1): temp_18_june.nc ...

Grids (5) <dimension family> : <associated variables> 

[1]   D3,D2,D1,D0 : thetao    **ACTIVE GRID** ( 489060  values per variable)
[2]   D0          : time
[3]   D1          : depth
[4]   D2          : lat
[5]   D3          : lon

Dimensions 4 (all active): 
  
  dim   name  length         min         max start count        dmin        dmax unlim coord_dim 
  <chr> <chr>  <dbl>       <dbl>       <dbl> <int> <int>       <dbl>       <dbl> <lgl> <lgl>     
1 D0    time       1 64409730    64409730        1     1 64409730    64409730    FALSE TRUE      
2 D1    depth      1        1.02        1.02     1     1        1.02        1.02 FALSE TRUE      
3 D2    lat      380       30.2        46.0      1   380       30.2        46.0  FALSE TRUE      
4 D3    lon     1287      -17.3        36.3      1  1287      -17.3        36.3  FALSE TRUE      

Explorar metadata

ncmeta::nc_grids('nc_files/temp_18_june.nc') %>% tidyr::unnest(cols = c(variables))

ncmeta::nc_vars('nc_files/temp_18_june.nc') 

Obten la información de las unidades de tiempo y luego utilízala para convertir los valores sin procesar en una fecha y hora

ncmeta::nc_atts('nc_files/temp_18_june.nc', "time")  %>%
  dplyr::select(value) %>% 
  flatten_df() %>% 
  pull(units)
[1] "minutes since 1900-01-01 00:00:00"
temp_june_22_t <- 
  sst_june_22 %>% 
  hyper_tibble() %>% 
  mutate(time = lubridate::ymd_hms("1900-01-01 00:00:00", tz = "UTC") + time * 60)

head(temp_june_22_t)

Grafica usando ggplot

# get countries data for the map
library(rnaturalearth)
library(rnaturalearthdata)
countries <- ne_countries(scale = "medium", returnclass = "sf")

# plot 
ggplot() +
  # add raster layer
  geom_tile(aes(x = lon, y = lat, fill = thetao), data = temp_june_22_t) +
  # define color palette of raster layer
  scale_fill_viridis_c(option = 'A', name = "°C") +
  # add countries layers
  geom_sf(
    fill = grey(0.9),
    color = grey(0.6),
    lwd = 0.2,
    data = countries
  ) +
  # define spatial extent
  coord_sf(
    xlim = range(temp_june_22_t$lon),
    ylim = range(temp_june_22_t$lat),
    expand = F,
    ndiscr = 500
  ) +
  labs(subtitle = "SST -  Sábado 18 Junio 2022",
       y = "Latitude", x = 'Longitude') +
  theme_minimal() 

Gráfica Python

import matplotlib.pyplot as plt
import xarray as xr
C:\Users\javiera\MINICO~1\lib\site-packages\xarray\backends\cfgrib_.py:27: UserWarning: Failed to load cfgrib - most likely there is a problem accessing the ecCodes library. Try `import cfgrib` to get the full error message
  warnings.warn(
import warnings
warnings.filterwarnings('ignore')


# satellite observation dataset
path_sat = 'nc_files/temp_18_june.nc'

# Open the files and store them in a Python variable
sat = xr.open_dataset(path_sat)
print(sat)
<xarray.Dataset>
Dimensions:  (depth: 1, time: 1, lat: 380, lon: 1287)
Coordinates:
  * depth    (depth) float32 1.018
  * lon      (lon) float32 -17.29 -17.25 -17.21 -17.17 ... 36.21 36.25 36.29
  * time     (time) datetime64[ns] 2022-06-18T23:30:00
  * lat      (lat) float32 30.19 30.23 30.27 30.31 ... 45.85 45.9 45.94 45.98
Data variables:
    thetao   (time, depth, lat, lon) float32 ...
Attributes:
    title:                           Potential Temperature (3D) - Hourly Mean
    FROM_ORIGINAL_FILE__field_type:  hourly_mean_centered_at_time_field
    source:                          MFS EAS6
    institution:                     Centro Euro-Mediterraneo sui Cambiamenti...
    contact:                         servicedesk.cmems@mercator-ocean.eu
    references:                      Clementi, E., Aydogdu, A., Goglio, A. C....
    comment:                         Please check in CMEMS catalogue the INFO...
    Conventions:                     CF-1.0
    bulletin_date:                   20220615
    bulletin_type:                   forecast
    _CoordSysBuilder:                ucar.nc2.dataset.conv.CF1Convention
    history:                         Data extracted from dataset http://local...
sat.thetao.sel(time='2022-06-18').plot()
<matplotlib.collections.QuadMesh object at 0x000001BB951A30D0>
plt.show()

Plot vertical profiles

Ahora usamos los datos del modelo para trazar perfiles verticales de clorofila.

Primero leemos los datos y comprobamos las unidades de tiempo para corregirlos.

path <- 'nc_files/mod_bio.nc'
mod_bio_dat <- tidync(path)

ncmeta::nc_grids(path) %>% tidyr::unnest(cols = c(variables))
ncmeta::nc_vars(path)

ncmeta::nc_atts(path, "time")  %>%
  dplyr::select(value) %>% 
  flatten_df() %>% 
  pull(units)
[1] "seconds since 1970-01-01 00:00:00"

Luego definimos un localidad y dos fechas para la gráfica usando la función hyper_filter del paquete tidync.

mod_bio_dat_t <- 
mod_bio_dat %>% 
  hyper_filter(longitude  = between(longitude, 5, 5), 
               latitude = between(latitude, 42.02, 42.03)) %>% 
  hyper_tibble() %>% 
  mutate(time = lubridate::ymd_hms("1970-01-01 00:00:00", tz = "UTC") + time,
         month = lubridate::month(time))

We can now plot the vertical profile using ggplot

ggplot(mod_bio_dat_t, aes(depth, chl, color = factor(month))) +
  geom_path () +
  coord_flip() +
  scale_x_reverse() +
  labs(x= 'Depth (m)', y = Chl-italic(a)~(mg/m^3), parse = T) +
  theme_minimal() +
  scale_color_discrete(name = "month")

Trazar cortes verticales

Este script se utiliza para trazar la temperatura en un corte vertical a lo largo de una línea longitudinal.

Primero definimos las coordenadas del corte vertical usando la función hyper_filter.

Luego lo convertimos en un tibble usando hyper_tibble y convertimos el formato de hora.

## plot vertical slice
mod_bio_dat_slice <-
  mod_bio_dat %>%
  hyper_filter(
    longitude  = between(longitude, 13.2, 16.5),
    latitude = between(latitude, 43.68, 43.7)
  ) %>%
  hyper_tibble() %>%
  mutate(
    time = lubridate::ymd_hms("1970-01-01 00:00:00", tz = "UTC") + time,
    month = lubridate::month(time)
  )

Ahora usamos geom_tile del paquete ggplot para visualizar el perfil vertical de temperatura a lo largo del corte longitudinal.

ggplot(mod_bio_dat_slice,aes(depth, longitude, fill  = chl ))+
  geom_tile(width = 8) +
  coord_flip() +
  scale_x_reverse() +
  labs(x = 'Depth (m)',
       y = "Longitude",
       parse = T) +
  theme_minimal() +
  scale_fill_viridis_c(name = Chl - italic(a) ~ (mg / m ^ 3))

LS0tDQp0aXRsZTogIlVzYW5kbyBQeXRob24gZGVzZGUgUiBlbiBlbCBjb250ZXh0byBkZSBDb3Blcm5pY3VzIg0KYXV0aG9yOiAiSmF2aWVyIEF0YWxhaCINCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCLCAlWScpYCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiFbXShpbWFnZXMvbG9nby11YS5qcGcpDQoNCiMjIEludGVyZmF6IGRlIFIgYSBQeXRob24gdXNhbmRvIFJldGljdWxhdGUNCg0KRWwgcGFxdWV0ZSByZXRpY3VsYXRlIHByb3BvcmNpb25hIHVuYSBpbnRlcmZheiBSIHBhcmEgbcOzZHVsb3MsIGNsYXNlcyB5IGZ1bmNpb25lcyBkZSBQeXRob24uDQoNCjxodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL3JldGljdWxhdGUvPg0KDQpFbCBwcmltZXIgcGFzbyBlcyBpbnN0YWxhciBQeXRob24gYSB0cmF2ZXMgZGUgcG9yIGVqZW1wbG8gQW5hY29uZGEuIDxodHRwczovL3d3dy5hbmFjb25kYS5jb20vcHJvZHVjdHMvZGlzdHJpYnV0aW9uPg0KDQpMdWVnbyBpbnN0YWxhciBlbCBwYXF1ZXRlIHJldGljdWxhdGUgZGVzZGUgQ1JBTiB1c2FuZG8gYGluc3RhbGwucGFja2FnZXMoJ3JldGljdWxhdGUnKWANCg0KIVtdKGltYWdlcy9yZXRpY3VsYXRlZF9weXRob24ucG5nKXt3aWR0aD0iMjkxIn0NCg0KIyMgVmVyc2lvbiBkZSBQeXRob24NCg0KRGUgbWFuZXJhIHByZWRldGVybWluYWRhLCByZXRpY3VsYXRlIHVzYSBsYSB2ZXJzacOzbiBkZSBQeXRob24gcXVlIHNlIGVuY3VlbnRyYSBlbiBlbCBQQVRIIGBTeXMud2hpY2goInB5dGhvbiIpYA0KDQpMYSBmdW5jacOzbiBgdXNlX3B5dGhvbigpYCB0ZSBwZXJtaXRlIGVzcGVjaWZpY2FyIHVuYSB2ZXJzacOzbiBhbHRlcm5hdGl2YSwgcG9yIGVqZW1wbG86DQoNCmBsaWJyYXJ5KHJldGljdWxhdGUpYA0KDQpgdXNlX3B5dGhvbigiL3Vzci9sb2NhbC9iaW4vcHl0aG9uIilgDQoNCmBgYHtyfQ0KbGlicmFyeShyZXRpY3VsYXRlKQ0KYGBgDQoNCiMjIEZ1bmNpb25lcyBiw6FzaWNhcyB1c2FuZG8gUHl0aG9uIGNodW5rcw0KDQpgYGB7cHl0aG9ufQ0KYSA9ICJIb2xhICIgKyAiTXVuZG8iDQpwcmludChhKQ0KYGBgDQoNCiMjIEVqZW1wbG8gZGUgdW5hIGdyw6FmaWNhIFB5dGhvbiBNYXRwbG90bGliDQoNCmBgYHtweXRob259DQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQppbXBvcnQgbnVtcHkgYXMgbnANCg0KIyBEYXRhIGZvciBwbG90dGluZw0KdCA9IG5wLmFyYW5nZSgwLjAsIDIuMCwgMC4wMSkNCnMgPSAxICsgbnAuc2luKDIgKiBucC5waSAqIHQpDQoNCmZpZywgYXggPSBwbHQuc3VicGxvdHMoKQ0KYXgucGxvdCh0LCBzKQ0KDQpheC5zZXQoeGxhYmVsPSd0aW1lIChzKScsIHlsYWJlbD0ndm9sdGFnZSAobVYpJykNCmF4LmdyaWQoKQ0KDQpwbHQuc2hvdygpDQoNCmBgYA0KDQojIyBBY2NlZGVyIGEgQ29wZXJuaWN1cyBDb3Blcm5pY3VzIFtNYXJpbmUgU2VydmljZSBDYXRhbG9ndWVdKGh0dHBzOi8vcmVzb3VyY2VzLm1hcmluZS5jb3Blcm5pY3VzLmV1L3Byb2R1Y3RzKQ0KDQpTaSBubyB0aWVuZSBtb3R1Y2xpZW50IGVuIFB5dGhvbiwgZGViZXLDoSBpbnN0YWxhcmxvIHVzYW5kbzoNCg0KYHB5dGhvbiAtbSBwaXAgaW5zdGFsbCBtb3R1Y2xpZW50PT0xLjguNCAtLW5vLWNhY2hlLWRpcmANCg0KRGViZXLDoXMgcmVlbXBsYXphciB0dXMgY3JlZGVuY2lhbGVzIGRlIGluaWNpbyBkZSBzZXNpw7NuIGRlIENvcGVybmljdXMgZW4gZWwgY8OzZGlnbyBkZSBhYm9qbyB5IGNvcnJlbG8gZW4gdW4gY2h1Y2sgZGUgUHl0aG9uLg0KDQpUYW1iacOpbiBwdWVkZXMgY2FtYmlhciBlbCBub21icmUgZGVsIGFyY2hpdm8geSBlbCBkaXJlY3RvcmlvIGRlIHNhbGlkYSBzZWfDum4gc3VzIG5lY2VzaWRhZGVzLg0KDQogICAgaW1wb3J0IG1vdHVjbGllbnQNCg0KICAgICFweXRob24gLW0gbW90dWNsaWVudCAtLW1vdHUgaHR0cHM6Ly9ucnQuY21lbXMtZHUuZXUvbW90dS13ZWIvTW90dSAtLXNlcnZpY2UtaWQgTUVEU0VBX0FOQUxZU0lTRk9SRUNBU1RfUEhZXzAwNl8wMTMtVERTIC0tcHJvZHVjdC1pZCBtZWQtY21jYy10ZW0tYW4tZmMtaCAtLWxvbmdpdHVkZS1taW4gLTE3LjI5MTcgLS1sb25naXR1ZGUtbWF4IDM2LjI5MTcgLS1sYXRpdHVkZS1taW4gMzAuMTg3NSAtLWxhdGl0dWRlLW1heCA0NS45NzkyIC0tZGF0ZS1taW4gIjIwMjItMDYtMTggMjM6MzA6MDAiIC0tZGF0ZS1tYXggIjIwMjItMDYtMTggMjM6MzA6MDAiIC0tZGVwdGgtbWluIDEuMDE4MiAtLWRlcHRoLW1heCAxLjAxODMgLS12YXJpYWJsZSB0aGV0YW8gLS1vdXQtZGlyIG5jX2ZpbGVzIC0tb3V0LW5hbWUgdGVtcF8xOF9qdW5lLm5jIC0tdXNlciBqYXRhbGFoIC0tcHdkICoqKioqKioqKioqDQoNCiMjIENvbnZlcnRpciBmaWNoZXJvIE5ldENERiBhIHRpYmJsZQ0KDQpQcmltZXJvIHNlIGluc3RhbGEgZWwgcGFxdWV0ZSB0aWR5bmMgdXNhbmRvOg0KDQpgaW5zdGFsbC5wYWNrYWdlcygidGlkeW5jIilgDQoNClZlciB0dXRvcmlhbCBlbiBlc3RlIGJsb2cgPGh0dHBzOi8vcm9wZW5zY2kub3JnL2Jsb2cvMjAxOS8xMS8wNS90aWR5bmMvPg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeW5jKQ0KDQpzc3RfanVuZV8yMiA8LSB0aWR5bmMoJ25jX2ZpbGVzL3RlbXBfMThfanVuZS5uYycpDQoNCnNzdF9qdW5lXzIyDQpgYGANCg0KRXhwbG9yYXIgbWV0YWRhdGENCg0KYGBge3J9DQpuY21ldGE6Om5jX2dyaWRzKCduY19maWxlcy90ZW1wXzE4X2p1bmUubmMnKSAlPiUgdGlkeXI6OnVubmVzdChjb2xzID0gYyh2YXJpYWJsZXMpKQ0KDQpuY21ldGE6Om5jX3ZhcnMoJ25jX2ZpbGVzL3RlbXBfMThfanVuZS5uYycpIA0KYGBgDQoNCk9idGVuIGxhIGluZm9ybWFjacOzbiBkZSBsYXMgdW5pZGFkZXMgZGUgdGllbXBvIHkgbHVlZ28gdXRpbMOtemFsYSBwYXJhIGNvbnZlcnRpciBsb3MgdmFsb3JlcyBzaW4gcHJvY2VzYXIgZW4gdW5hIGZlY2hhIHkgaG9yYQ0KDQpgYGB7cn0NCm5jbWV0YTo6bmNfYXR0cygnbmNfZmlsZXMvdGVtcF8xOF9qdW5lLm5jJywgInRpbWUiKSAgJT4lDQogIGRwbHlyOjpzZWxlY3QodmFsdWUpICU+JSANCiAgZmxhdHRlbl9kZigpICU+JSANCiAgcHVsbCh1bml0cykNCg0KdGVtcF9qdW5lXzIyX3QgPC0gDQogIHNzdF9qdW5lXzIyICU+JSANCiAgaHlwZXJfdGliYmxlKCkgJT4lIA0KICBtdXRhdGUodGltZSA9IGx1YnJpZGF0ZTo6eW1kX2htcygiMTkwMC0wMS0wMSAwMDowMDowMCIsIHR6ID0gIlVUQyIpICsgdGltZSAqIDYwKQ0KDQpoZWFkKHRlbXBfanVuZV8yMl90KQ0KYGBgDQoNCiMjIEdyYWZpY2EgdXNhbmRvIGdncGxvdA0KDQpgYGB7cn0NCiMgZ2V0IGNvdW50cmllcyBkYXRhIGZvciB0aGUgbWFwDQpsaWJyYXJ5KHJuYXR1cmFsZWFydGgpDQpsaWJyYXJ5KHJuYXR1cmFsZWFydGhkYXRhKQ0KY291bnRyaWVzIDwtIG5lX2NvdW50cmllcyhzY2FsZSA9ICJtZWRpdW0iLCByZXR1cm5jbGFzcyA9ICJzZiIpDQoNCiMgcGxvdCANCmdncGxvdCgpICsNCiAgIyBhZGQgcmFzdGVyIGxheWVyDQogIGdlb21fdGlsZShhZXMoeCA9IGxvbiwgeSA9IGxhdCwgZmlsbCA9IHRoZXRhbyksIGRhdGEgPSB0ZW1wX2p1bmVfMjJfdCkgKw0KICAjIGRlZmluZSBjb2xvciBwYWxldHRlIG9mIHJhc3RlciBsYXllcg0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAnQScsIG5hbWUgPSAiwrBDIikgKw0KICAjIGFkZCBjb3VudHJpZXMgbGF5ZXJzDQogIGdlb21fc2YoDQogICAgZmlsbCA9IGdyZXkoMC45KSwNCiAgICBjb2xvciA9IGdyZXkoMC42KSwNCiAgICBsd2QgPSAwLjIsDQogICAgZGF0YSA9IGNvdW50cmllcw0KICApICsNCiAgIyBkZWZpbmUgc3BhdGlhbCBleHRlbnQNCiAgY29vcmRfc2YoDQogICAgeGxpbSA9IHJhbmdlKHRlbXBfanVuZV8yMl90JGxvbiksDQogICAgeWxpbSA9IHJhbmdlKHRlbXBfanVuZV8yMl90JGxhdCksDQogICAgZXhwYW5kID0gRiwNCiAgICBuZGlzY3IgPSA1MDANCiAgKSArDQogIGxhYnMoc3VidGl0bGUgPSAiU1NUIC0gIFPDoWJhZG8gMTggSnVuaW8gMjAyMiIsDQogICAgICAgeSA9ICJMYXRpdHVkZSIsIHggPSAnTG9uZ2l0dWRlJykgKw0KICB0aGVtZV9taW5pbWFsKCkgDQpgYGANCg0KIyMgR3LDoWZpY2EgUHl0aG9uDQoNCmBgYHtweXRob24sIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02LCB3YXJuaW5ncyA9IEZ9DQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQppbXBvcnQgeGFycmF5IGFzIHhyDQppbXBvcnQgd2FybmluZ3MNCndhcm5pbmdzLmZpbHRlcndhcm5pbmdzKCdpZ25vcmUnKQ0KDQoNCiMgc2F0ZWxsaXRlIG9ic2VydmF0aW9uIGRhdGFzZXQNCnBhdGhfc2F0ID0gJ25jX2ZpbGVzL3RlbXBfMThfanVuZS5uYycNCg0KIyBPcGVuIHRoZSBmaWxlcyBhbmQgc3RvcmUgdGhlbSBpbiBhIFB5dGhvbiB2YXJpYWJsZQ0Kc2F0ID0geHIub3Blbl9kYXRhc2V0KHBhdGhfc2F0KQ0KcHJpbnQoc2F0KQ0KDQpzYXQudGhldGFvLnNlbCh0aW1lPScyMDIyLTA2LTE4JykucGxvdCgpDQpwbHQuc2hvdygpDQoNCmBgYA0KDQojIyBQbG90IHZlcnRpY2FsIHByb2ZpbGVzDQoNCkFob3JhIHVzYW1vcyBsb3MgZGF0b3MgZGVsIG1vZGVsbyBwYXJhIHRyYXphciBwZXJmaWxlcyB2ZXJ0aWNhbGVzIGRlIGNsb3JvZmlsYS4NCg0KUHJpbWVybyBsZWVtb3MgbG9zIGRhdG9zIHkgY29tcHJvYmFtb3MgbGFzIHVuaWRhZGVzIGRlIHRpZW1wbyBwYXJhIGNvcnJlZ2lybG9zLg0KDQpgYGB7cn0NCnBhdGggPC0gJ25jX2ZpbGVzL21vZF9iaW8ubmMnDQptb2RfYmlvX2RhdCA8LSB0aWR5bmMocGF0aCkNCg0KbmNtZXRhOjpuY19ncmlkcyhwYXRoKSAlPiUgdGlkeXI6OnVubmVzdChjb2xzID0gYyh2YXJpYWJsZXMpKQ0KbmNtZXRhOjpuY192YXJzKHBhdGgpDQoNCm5jbWV0YTo6bmNfYXR0cyhwYXRoLCAidGltZSIpICAlPiUNCiAgZHBseXI6OnNlbGVjdCh2YWx1ZSkgJT4lIA0KICBmbGF0dGVuX2RmKCkgJT4lIA0KICBwdWxsKHVuaXRzKQ0KYGBgDQoNCkx1ZWdvIGRlZmluaW1vcyB1biBsb2NhbGlkYWQgeSBkb3MgZmVjaGFzIHBhcmEgbGEgZ3LDoWZpY2EgdXNhbmRvIGxhIGZ1bmNpw7NuIGBoeXBlcl9maWx0ZXJgIGRlbCBwYXF1ZXRlIGB0aWR5bmNgLg0KDQpgYGB7cn0NCm1vZF9iaW9fZGF0X3QgPC0gDQptb2RfYmlvX2RhdCAlPiUgDQogIGh5cGVyX2ZpbHRlcihsb25naXR1ZGUgID0gYmV0d2Vlbihsb25naXR1ZGUsIDUsIDUpLCANCiAgICAgICAgICAgICAgIGxhdGl0dWRlID0gYmV0d2VlbihsYXRpdHVkZSwgNDIuMDIsIDQyLjAzKSkgJT4lIA0KICBoeXBlcl90aWJibGUoKSAlPiUgDQogIG11dGF0ZSh0aW1lID0gbHVicmlkYXRlOjp5bWRfaG1zKCIxOTcwLTAxLTAxIDAwOjAwOjAwIiwgdHogPSAiVVRDIikgKyB0aW1lLA0KICAgICAgICAgbW9udGggPSBsdWJyaWRhdGU6Om1vbnRoKHRpbWUpKQ0KYGBgDQoNCldlIGNhbiBub3cgcGxvdCB0aGUgdmVydGljYWwgcHJvZmlsZSB1c2luZyBgZ2dwbG90YA0KDQpgYGB7ciBmaWcud2lkdGg9NCwgZmlnLmhlaWdodD02fQ0KZ2dwbG90KG1vZF9iaW9fZGF0X3QsIGFlcyhkZXB0aCwgY2hsLCBjb2xvciA9IGZhY3Rvcihtb250aCkpKSArDQogIGdlb21fcGF0aCAoKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIHNjYWxlX3hfcmV2ZXJzZSgpICsNCiAgbGFicyh4PSAnRGVwdGggKG0pJywgeSA9IENobC1pdGFsaWMoYSl+KG1nL21eMyksIHBhcnNlID0gVCkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lID0gIm1vbnRoIikNCmBgYA0KDQojIyBUcmF6YXIgY29ydGVzIHZlcnRpY2FsZXMNCg0KRXN0ZSBzY3JpcHQgc2UgdXRpbGl6YSBwYXJhIHRyYXphciBsYSB0ZW1wZXJhdHVyYSBlbiB1biBjb3J0ZSB2ZXJ0aWNhbCBhIGxvIGxhcmdvIGRlIHVuYSBsw61uZWEgbG9uZ2l0dWRpbmFsLg0KDQpQcmltZXJvIGRlZmluaW1vcyBsYXMgY29vcmRlbmFkYXMgZGVsIGNvcnRlIHZlcnRpY2FsIHVzYW5kbyBsYSBmdW5jacOzbiBgaHlwZXJfZmlsdGVyYC4NCg0KTHVlZ28gbG8gY29udmVydGltb3MgZW4gdW4gdGliYmxlIHVzYW5kbyBgaHlwZXJfdGliYmxlYCB5IGNvbnZlcnRpbW9zIGVsIGZvcm1hdG8gZGUgaG9yYS4NCg0KYGBge3J9DQojIyBwbG90IHZlcnRpY2FsIHNsaWNlDQptb2RfYmlvX2RhdF9zbGljZSA8LQ0KICBtb2RfYmlvX2RhdCAlPiUNCiAgaHlwZXJfZmlsdGVyKA0KICAgIGxvbmdpdHVkZSAgPSBiZXR3ZWVuKGxvbmdpdHVkZSwgMTMuMiwgMTYuNSksDQogICAgbGF0aXR1ZGUgPSBiZXR3ZWVuKGxhdGl0dWRlLCA0My42OCwgNDMuNykNCiAgKSAlPiUNCiAgaHlwZXJfdGliYmxlKCkgJT4lDQogIG11dGF0ZSgNCiAgICB0aW1lID0gbHVicmlkYXRlOjp5bWRfaG1zKCIxOTcwLTAxLTAxIDAwOjAwOjAwIiwgdHogPSAiVVRDIikgKyB0aW1lLA0KICAgIG1vbnRoID0gbHVicmlkYXRlOjptb250aCh0aW1lKQ0KICApDQoNCg0KYGBgDQoNCkFob3JhIHVzYW1vcyBgZ2VvbV90aWxlYCBkZWwgcGFxdWV0ZSBgZ2dwbG90YCBwYXJhIHZpc3VhbGl6YXIgZWwgcGVyZmlsIHZlcnRpY2FsIGRlIHRlbXBlcmF0dXJhIGEgbG8gbGFyZ28gZGVsIGNvcnRlIGxvbmdpdHVkaW5hbC4NCg0KYGBge3J9DQpnZ3Bsb3QobW9kX2Jpb19kYXRfc2xpY2UsYWVzKGRlcHRoLCBsb25naXR1ZGUsIGZpbGwgID0gY2hsICkpKw0KICBnZW9tX3RpbGUod2lkdGggPSA4KSArDQogIGNvb3JkX2ZsaXAoKSArDQogIHNjYWxlX3hfcmV2ZXJzZSgpICsNCiAgbGFicyh4ID0gJ0RlcHRoIChtKScsDQogICAgICAgeSA9ICJMb25naXR1ZGUiLA0KICAgICAgIHBhcnNlID0gVCkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhuYW1lID0gQ2hsIC0gaXRhbGljKGEpIH4gKG1nIC8gbSBeIDMpKQ0KYGBgDQo=