Go back to the Contents page.


Press Show to reveal the code chunks.


# Set seed for reproducibility
set.seed(1982) 
# Set global options for all code chunks
knitr::opts_chunk$set(
  # Disable messages printed by R code chunks
  message = FALSE,    
  # Disable warnings printed by R code chunks
  warning = FALSE,    
  # Show R code within code chunks in output
  echo = TRUE,        
  # Include both R code and its results in output
  include = TRUE,     
  # Evaluate R code chunks
  eval = TRUE,       
  # Enable caching of R code chunks for faster rendering
  cache = FALSE,      
  # Align figures in the center of the output
  fig.align = "center",
  # Enable retina display for high-resolution figures
  retina = 2,
  # Show errors in the output instead of stopping rendering
  error = TRUE,
  # Do not collapse code and output into a single block
  collapse = FALSE
)
# Start the figure counter
fig_count <- 0
# Define the captioner function
captioner <- function(caption) {
  fig_count <<- fig_count + 1
  paste0("Figure ", fig_count, ": ", caption)
}
# remotes::install_github("davidbolin/rspde", ref = "devel")
# remotes::install_github("davidbolin/metricgraph", ref = "devel")
library(rSPDE)
library(MetricGraph)
library(grateful)

library(ggplot2)
library(reshape2)
library(plotly)

1 Fractional diffusion equations on metric graphs

We analyze and numerically approximate solutions to fractional diffusion equations on metric graphs of the form \[\begin{equation} \label{eq:maineq} \tag{1} \left\{ \begin{aligned} \partial_t u(s,t) + (\kappa^2 - \Delta_\Gamma)^{\alpha/2} u(s,t) &= f(s,t), && \quad (s,t) \in \Gamma \times (0, T), \\ u(s,0) &= u_0(s), && \quad s \in \Gamma, \end{aligned} \right. \end{equation}\] with \(u(\cdot,t)\) satisfying the Kirchhoff vertex conditions \[\begin{equation} \label{eq:Kcond} \tag{2} \mathcal{K} = \left\{\phi\in C(\Gamma)\;\middle|\; \forall v\in \mathcal{V}:\; \sum_{e\in\mathcal{E}_v}\partial_e \phi(v)=0 \right\}. \end{equation}\] Here \(\Gamma = (\mathcal{V},\mathcal{E})\) is a metric graph, \(\kappa>0\), \(\alpha\in(0,2]\) determines the smoothness of \(u(\cdot,t)\), \(\Delta_{\Gamma}\) is the so-called Kirchhoff–Laplacian, and \(f:\Gamma\times (0,T)\to\mathbb{R}\) and \(u_0: \Gamma \to \mathbb{R}\) are fixed functions, called right-hand side and initial condition, respectively.

2 Numerical Scheme

Let \(\alpha\in(0,2]\) and \(U_h^\tau\) denote the sequence of approximations of the solution to the weak form of problem \(\eqref{eq:maineq}\) at each time step on a mesh indexed by \(h\). The scheme computes \(U_h^{\tau}\subset V_h\), which solves \[\begin{equation} \left\{ \begin{aligned} \langle\delta U_h^{k+1},\phi\rangle + \mathfrak{a}( U_h^{k+1},\phi) &= \langle f^{k+1},\phi\rangle, \quad\forall\phi\in V_h,\quad k=0,\dots, N-1, \\ U^0_h &= P_hu_0, \end{aligned} \right. \end{equation}\] where \(f^{k+1} = \displaystyle\dfrac{1}{\tau}\int_{t_k}^{t_{k+1}}f(t)dt\). The above expression can be equivalently written as \[\begin{equation} \left\{ \begin{aligned} \langle\dfrac{U_h^{k+1} - U_h^{k}}{\tau},\phi\rangle + \mathfrak{a}( U_h^{k+1},\phi) &= \langle f^{k+1},\phi\rangle, \quad\forall\phi\in V_h,\quad k=0,\dots, N-1, \\ U^0_h &= P_hu_0, \end{aligned} \right. \end{equation}\] or \[\begin{equation} \label{system:fully_discrete_scheme} \tag{3} \left\{ \begin{aligned} \langle U_h^{k+1},\phi\rangle + \tau\mathfrak{a}( U_h^{k+1},\phi) &= \langle U_h^{k},\phi\rangle + \tau\langle f^{k+1},\phi\rangle, \quad\forall\phi\in V_h,\quad k=0,\dots, N-1, \\ U^0_h &= P_hu_0. \end{aligned} \right. \end{equation}\]

At each time step \(t_k\), the finite element solution \(U_h^k\in V_h\) to \(\eqref{system:fully_discrete_scheme}\) can be expressed as a linear combination of the basis functions \(\{\psi^i_h\}_{i=1}^{N_h}\) introduced in the Preliminaries page, namely, \[\begin{align} \label{num_sol} \tag{4} U_h^k(s) = \sum_{i=1}^{N_h}u_i^k\psi^i_h(s), \;s\in\Gamma. \end{align}\]

Replacing \(\eqref{num_sol}\) into \(\eqref{system:fully_discrete_scheme}\) yields the following system \[\begin{align*} \sum_{j=1}^{N_h}u_j^{k+1}[(\psi_h^j,\psi_h^i)_{L_2(\Gamma)}+ \tau\mathfrak{a}(\psi_h^j,\psi_h^i)] = \sum_{j=1}^{N_h}u_j^{k}(\psi_h^j,\psi_h^i)_{L_2(\Gamma)}+\tau( f^{k+1},\psi_h^i)_{L_2(\Gamma)},\quad i = 1,\dots, N_h. \end{align*}\] In matrix notation, \[\begin{align} \label{diff_eq_discrete} (\mathbf{C}+\tau \mathbf{L}^{\alpha/2})\mathbf{U}^{k+1} = \mathbf{C}\mathbf{U}^k+\tau \mathbf{F}^{k+1}, \end{align}\] or by introducing the scaling parameter \(\kappa^2>0\), \[\begin{align} (\mathbf{C}+\tau (\kappa^2)^{\alpha/2}(\mathbf{L}/\kappa^2)^{\alpha/2})\mathbf{U}^{k+1} = \mathbf{C}\mathbf{U}^k+\tau \mathbf{F}^{k+1}, \end{align}\] where \(\mathbf{C}\) has entries \(\mathbf{C}_{i,j} = (\psi_h^i,\psi_h^j)_{L_2(\Gamma)}\), \(\mathbf{L}^{\alpha/2}\) has entries \(\mathfrak{a}(\psi_h^i,\psi_h^j)\), \(\mathbf{U}^k\) has components \(u_j^k\), and \(\mathbf{F}^k\) has components \(( f^{k},\psi_h^i)_{L_2(\Gamma)}\). Applying \((\mathbf{L}/\kappa^2)^{-\alpha/2}\) to both sides yields \[\begin{equation} ((\mathbf{L}/\kappa^2)^{-\alpha/2}\mathbf{C}+\tau (\kappa^2)^{\alpha/2}\mathbf{I})\mathbf{U}^{k+1} = (\mathbf{L}/\kappa^2)^{-\alpha/2}(\mathbf{C}\mathbf{U}^k+\tau \mathbf{F}^{k+1}). \end{equation}\] Following Bolin and Kirchner (2020), we approximate \((\mathbf{L}/\kappa^2)^{-\alpha/2}\) by \(\mathbf{P}_\ell^{-\top}\mathbf{P}_r^\top\) to arrive at \[\begin{equation} \label{eq:scheme2} \tag{5} (\mathbf{P}_\ell^{-\top}\mathbf{P}_r^\top \mathbf{C}+\tau(\kappa^2)^{\alpha/2} \mathbf{I})\mathbf{U}^{k+1} = \mathbf{P}_\ell^{-\top}\mathbf{P}_r^\top(\mathbf{C}\mathbf{U}^k+\tau \mathbf{F}^{k+1}). \end{equation}\] where \[\begin{equation} \label{eq:PLPR} \tag{6} \mathbf{P}_r = \prod_{i=1}^m \left(\mathbf{I}-r_{1i}\dfrac{\mathbf{C}^{-1}\mathbf{L}}{\kappa^2}\right)\quad\text{and}\quad \mathbf{P}_\ell = \dfrac{1}{\texttt{factor}}\mathbf{C}\prod_{j=1}^{m+1} \left(\mathbf{I}-r_{2j}\dfrac{\mathbf{C}^{-1}\mathbf{L}}{\kappa^2}\right), \end{equation}\] and \(\texttt{factor} = \dfrac{c_m}{b_{m+1}}\), and \(\{r_{1i}\}_{i=1}^m\) and \(\{r_{2j}\}_{j=1}^{m+1}\) are the roots of \(q_1(x) =\sum_{i=0}^mc_ix^{i}\) and \(q_2(x)=\sum_{j=0}^{m+1}b_jx^{j}\), respectively. The coefficients \(\{c_i\}_{i=0}^m\) and \(\{b_j\}_{j=0}^{m+1}\) are determined as the best rational approximation \(q_1/q_2\) of the function \(x^{\alpha/2-1}\) over the interval \(J_h: = [\kappa^{2}\lambda_{N_h,h}^{-1}, \kappa^{2}\lambda_{1,h}^{-1}]\), where \(\lambda_{1,h}, \lambda_{N_h,h}>0\) are the smallest and the largest eigenvalue of \(L_h\), respectively.

For the sake of clarity, we note that the numerical implementation of Bolin and Kirchner (2020) actually defines \(\mathbf{P}_r\) and \(\mathbf{P}_\ell\) as \[\begin{equation} \label{eq:PLPRbolin} \tag{7} \mathbf{P}_r = \prod_{i=1}^m \left(\mathbf{I}-r_{1i}\dfrac{\mathbf{C}^{-1}\mathbf{L}}{\kappa^2}\right)\quad\text{and}\quad \mathbf{P}_\ell = \dfrac{\kappa^{2\beta}}{\texttt{factor}}\mathbf{C}\prod_{j=1}^{m+1} \left(\mathbf{I}-r_{2j}\dfrac{\mathbf{C}^{-1}\mathbf{L}}{\kappa^2}\right), \end{equation}\] where \(\beta = \alpha/2\) and the scaling factor \((\kappa^2)^{\alpha/2}\) or \(\kappa^{2\beta}\) is already incorporated in \(\mathbf{P}_\ell\), a convention we adopt in the following. With this under consideration, we can rewrite \(\eqref{eq:scheme2}\) as \[\begin{equation} \tag{8} (\mathbf{P}_r^\top \mathbf{C}+\tau \mathbf{P}_\ell^\top)\mathbf{U}^{k+1} = \mathbf{P}_r^\top(\mathbf{C}\mathbf{U}^k+\tau \mathbf{F}^{k+1}), \label{eq:scheme} \end{equation}\] where
\[\begin{equation} \mathbf{P}_r^\top = \prod_{i=1}^m \left(\mathbf{I}-r_{1i}\dfrac{\mathbf{L}\mathbf{C}^{-1}}{\kappa^2}\right)\quad\text{and}\quad \mathbf{P}_\ell^\top = \dfrac{\kappa^{2\beta}}{\texttt{factor}}\prod_{j=1}^{m+1} \left(\mathbf{I}-r_{2j}\dfrac{\mathbf{L}\mathbf{C}^{-1}}{\kappa^2}\right)\cdot \mathbf{C} \end{equation}\] since \(\mathbf{L}\) and \(\mathbf{C}^{-1}\) are symmetric and the factors in the product commute. Replacing these two into \(\eqref{eq:scheme}\) yields \[\begin{equation} \left(\prod_{i=1}^m \left(\mathbf{I}-r_{1i}\dfrac{\mathbf{L}\mathbf{C}^{-1}}{\kappa^2}\right)+\dfrac{\tau \kappa^{2\beta}}{\texttt{factor}}\prod_{j=1}^{m+1} \left(\mathbf{I}-r_{2j}\dfrac{\mathbf{L}\mathbf{C}^{-1}}{\kappa^2}\right)\right)\mathbf{C}\mathbf{U}^{k+1} = \prod_{i=1}^m \left(\mathbf{I}-r_{1i}\dfrac{\mathbf{L}\mathbf{C}^{-1}}{\kappa^2}\right)\cdot (\mathbf{C}\mathbf{U}^k+\tau \mathbf{F}^{k+1}), \end{equation}\] that is, \[\begin{equation} \label{eq:final_scheme} \tag{9} \mathbf{U}^{k+1} = \mathbf{C}^{-1}\left(\prod_{i=1}^m \left(\mathbf{I}-r_{1i}\dfrac{\mathbf{L}\mathbf{C}^{-1}}{\kappa^2}\right)+\dfrac{\tau \kappa^{2\beta}}{\texttt{factor}}\prod_{j=1}^{m+1} \left(\mathbf{I}-r_{2j}\dfrac{\mathbf{L}\mathbf{C}^{-1}}{\kappa^2}\right)\right)^{-1} \prod_{i=1}^m \left(\mathbf{I}-r_{1i}\dfrac{\mathbf{L}\mathbf{C}^{-1}}{\kappa^2}\right)\cdot (\mathbf{C}\mathbf{U}^k+\tau \mathbf{F}^{k+1}). \end{equation}\] Considering the partial fraction decomposition \[\begin{equation} \label{eq:partial_fraction} \tag{10} \dfrac{\prod_{i=1}^m (1-r_{1i}x)}{\prod_{i=1}^m (1-r_{1i}x)+\dfrac{\tau \kappa^{2\beta}}{\texttt{factor}} \prod_{j=1}^{m+1} (1-r_{2j}x)}=\sum_{k=1}^{m+1} a_k(x-p_k)^{-1} + r, \end{equation}\] scheme \(\eqref{eq:final_scheme}\) can be expressed as \[\begin{equation} \label{eq:final_scheme2} \tag{11} \mathbf{U}^{k+1} = \mathbf{C}^{-1}\left(\sum_{k=1}^{m+1} a_k\left( \dfrac{\mathbf{L}\mathbf{C}^{-1}}{\kappa^2}-p_k\mathbf{I}\right)^{-1} + r\mathbf{I}\right) (\mathbf{C}\mathbf{U}^k+\tau \mathbf{F}^{k+1}) \end{equation}\]

In practice, since the rational function in \(\eqref{eq:partial_fraction}\) is proper, there is no remainder \(r\). Moreover, since \(\left( \dfrac{\mathbf{L}\mathbf{C}^{-1}}{\kappa^2}-p_k\mathbf{I}\right)^{-1} = \mathbf{C}\left( \dfrac{\mathbf{L}}{\kappa^2}-p_k\mathbf{C}\right)^{-1}\), we have that \(\eqref{eq:final_scheme2}\) can be rewritten as

\[\begin{equation} \label{eq:final_scheme3} \tag{12} \mathbf{U}^{k+1} = \mathbf{R}(\mathbf{C}\mathbf{U}^k+\tau \mathbf{F}^{k+1}),\quad \mathbf{R} = \sum_{k=1}^{m+1} a_k\left( \dfrac{\mathbf{L}}{\kappa^2}-p_k\mathbf{C}\right)^{-1}. \end{equation}\]

3 Numerical implementation

3.1 Function my.get.roots()

For each rational order \(m\) (1,2,3,4) and smoothness parameter \(\beta\) (= \(\alpha/2\) with \(\alpha\) between 0.5 and 2), function my.get.roots() (adapted from the rSPDE package) returns \(\texttt{factor} = \dfrac{c_m}{b_{m+1}}\), and the roots \(\{r_{1i}\}_{i=1}^m\) and \(\{r_{2j}\}_{j=1}^{m+1}\).

# Function to compute the roots and factor for the rational approximation
my.get.roots <- function(m, # rational order, m = 1, 2, 3, or 4
                         beta # smoothness parameter, beta = alpha/2 with alpha between 0.5 and 2
                         ) {
  m1table <- rSPDE:::m1table
  m2table <- rSPDE:::m2table
  m3table <- rSPDE:::m3table
  m4table <- rSPDE:::m4table
  mt <- get(paste0("m", m, "table"))
  rb <- rep(0, m + 1)
  rc <- rep(0, m)
  if(m == 1) {
    rc = approx(mt$beta, mt[[paste0("rc")]], beta)$y
  } else {
    rc = sapply(1:m, function(i) {
      approx(mt$beta, mt[[paste0("rc.", i)]], beta)$y
    })
  }
  rb = sapply(1:(m+1), function(i) {
    approx(mt$beta, mt[[paste0("rb.", i)]], xout = beta)$y
  })
  factor = approx(mt$beta, mt$factor, xout = beta)$y
  return(list(pl_roots = rb, # roots \{r_{2j}\}_{j=1}^{m+1}
              pr_roots = rc, # roots \{r_{1i}\}_{i=1}^m
              factor = factor # this is c_m/b_{m+1}
              ))
}

3.2 Function poly.from.roots()

Function poly.from.roots() computes the coefficients of a polynomial from its roots.

# Function to compute polynomial coefficients from roots
poly.from.roots <- function(roots) {
  coef <- 1
  for (r in roots) {coef <- convolve(coef, c(1, -r), type = "open")}
  return(coef) # returned in increasing order like a+bx+cx^2+...
}

3.3 Function compute.partial.fraction.param()

Given factor\(=\texttt{factor} = \dfrac{c_m}{b_{m+1}}\), pr_roots\(=\{r_{1i}\}_{i=1}^m\), pl_roots\(=\{r_{2j}\}_{j=1}^{m+1}\), time_step\(=\tau\), and scaling\(=\kappa^{2\beta}\), function compute.partial.fraction.param() computes the parameters for the partial fraction decomposition \(\eqref{eq:partial_fraction}\).

# Function to compute the parameters for the partial fraction decomposition
compute.partial.fraction.param <- function(factor, # c_m/b_{m+1}
                                           pr_roots, # roots \{r_{1i}\}_{i=1}^m
                                           pl_roots, # roots \{r_{2j}\}_{j=1}^{m+1}
                                           time_step, # \tau
                                           scaling # \kappa^{2\beta}
                                           ) {
  pr_coef <- c(0, poly.from.roots(pr_roots))
  pl_coef <- poly.from.roots(pl_roots)
  factor_pr_coef <- pr_coef
  pr_plus_pl_coef <- factor_pr_coef + ((scaling * time_step)/factor) * pl_coef
  res <- gsignal::residue(factor_pr_coef, pr_plus_pl_coef)
  return(list(r = res$r, # residues \{a_k\}_{k=1}^{m+1}
              p = res$p, # poles \{p_k\}_{k=1}^{m+1}
              k = res$k # remainder r
              ))
}

3.4 Function my.fractional.operators.frac()

Given the Laplacian matrix L, the smoothness parameter beta, the mass matrix C (not lumped), the scaling factor scale.factor\(=\kappa^2\), the rational order m, and the time step time_step\(=\tau\), function my.fractional.operators.frac() computes the fractional operator and returns a list containing the necessary matrices and parameters for the fractional diffusion equation.

# Function to compute the fractional operator
my.fractional.operators.frac <- function(L, # Laplacian matrix
                                         beta, # smoothness parameter beta
                                         C, # mass matrix (not lumped)
                                         scale.factor, # scaling parameter = kappa^2
                                         m = 1, # rational order, m = 1, 2, 3, or 4
                                         time_step # time step = tau
                                         ) {
  I <- Matrix::Diagonal(dim(C)[1])
  L <- L / scale.factor
  if(beta == 1){
    L <- L * scale.factor^beta
    return(list(C = C, # mass matrix
                L = L, # Laplacian matrix scaled
                m = m, # rational order
                beta = beta, # smoothness parameter
                LHS = C + time_step * L # left-hand side of the linear system
                ))
  } else {
    scaling <- scale.factor^beta
    roots <- my.get.roots(m, beta)
    poles_rs_k <- compute.partial.fraction.param(roots$factor, roots$pr_roots, roots$pl_roots, time_step, scaling)

    partial_fraction_terms <- list()
    for (i in 1:(m+1)) {
      # Here is where the terms in the sum in eq 12 are computed
      partial_fraction_terms[[i]] <- (L - poles_rs_k$p[i] * C)#/poles_rs_k$r[i]
      }
    return(list(C = C, # mass matrix
                L = L, # Laplacian matrix scaled
                m = m, # rational order
                beta = beta, # smoothness parameter
                partial_fraction_terms = partial_fraction_terms, # partial fraction terms
                residues = poles_rs_k$r # residues \{a_k\}_{k=1}^{m+1}
                ))
  }
}

3.5 Function my.solver.frac()

Given the object returned by my.fractional.operators.frac() and a vector v, function my.solver.frac() solves the linear \(\eqref{eq:final_scheme2}\) for the vector v. If beta = 1, it solves the system directly; otherwise, it uses the partial fraction decomposition.

# Function to solve the iteration
my.solver.frac <- function(obj, # object returned by my.fractional.operators.frac()
                           v # vector to be solved for
                           ){
  beta <- obj$beta
  m <- obj$m
  if (beta == 1){
    return(solve(obj$LHS, v) # solve the linear system directly for beta = 1
           )
  } else {
    partial_fraction_terms <- obj$partial_fraction_terms
    residues <- obj$residues
    output <- v*0
    for (i in 1:(m+1)) {output <- output + residues[i] * solve(partial_fraction_terms[[i]], v)}
    return(output # solve the linear system using the partial fraction decomposition
           )
  }
}


# my.solver.frac <- function(obj, v) {
#   beta <- obj$beta
#   m <- obj$m
#   
#   if (beta == 1) {
#     return(solve(obj$LHS, v))
#   } 
#   
#   partial_fraction_terms <- obj$partial_fraction_terms
#   residues <- obj$residues
#   
#   # --- PARALLEL PART ---
#   results <- parallel::mclapply(
#     1:(m+1),
#     function(i) residues[i] * solve(partial_fraction_terms[[i]], v),
#     mc.cores = parallel::detectCores()  
#   )
#   
#   return(Reduce(`+`, results))
# }

3.6 Function solve_fractional_evolution()

Given the fractional operator object my_op_frac, a time step time_step, a sequence of time points time_seq, an initial value val_at_0, and the right-hand side matrix RHST, function solve_fractional_evolution() computes the solution to the fractional diffusion equation at each time step using scheme \(\eqref{eq:final_scheme2}\).

solve_fractional_evolution <- function(my_op_frac, time_step, time_seq, val_at_0, RHST) {
  CC <- my_op_frac$C
  SOL <- matrix(NA, nrow = nrow(CC), ncol = length(time_seq))
  SOL[, 1] <- val_at_0
  for (k in 1:(length(time_seq) - 1)) {
    rhs <- CC %*% SOL[, k] + time_step * RHST[, k + 1]
    SOL[, k + 1] <- as.matrix(my.solver.frac(my_op_frac, rhs))
  }
  return(SOL)
}

4 Auxiliary functions

4.1 Function gets.graph.tadpole()

Given a mesh size h, function gets.graph.tadpole() builds a tadpole graph and creates a mesh.

# Function to build a tadpole graph and create a mesh
gets.graph.tadpole <- function(h){
  edge1 <- rbind(c(0,0),c(1,0))
  theta <- seq(from=-pi,to=pi,length.out = 10000)
  edge2 <- cbind(1+1/pi+cos(theta)/pi,sin(theta)/pi)
  edges <- list(edge1, edge2)
  graph <- metric_graph$new(edges = edges, verbose = 0)
  graph$set_manual_edge_lengths(edge_lengths = c(1,2))
  graph$build_mesh(h = h)
  return(graph)
}

4.2 Function tadpole.eig()

Given a mode number k and a tadpole graph graph, function tadpole.eig() computes the eigenpairs of the tadpole graph.

# Function to compute the eigenfunctions of the tadpole graph
tadpole.eig <- function(k,graph){
x1 <- c(0,graph$get_edge_lengths()[1]*graph$mesh$PtE[graph$mesh$PtE[,1]==1,2]) 
x2 <- c(0,graph$get_edge_lengths()[2]*graph$mesh$PtE[graph$mesh$PtE[,1]==2,2]) 

if(k==0){ 
  f.e1 <- rep(1,length(x1)) 
  f.e2 <- rep(1,length(x2)) 
  f1 = c(f.e1[1],f.e2[1],f.e1[-1], f.e2[-1]) 
  f = list(phi=f1/sqrt(3)) 
  
} else {
  f.e1 <- -2*sin(pi*k*1/2)*cos(pi*k*x1/2) 
  f.e2 <- sin(pi*k*x2/2)                  
  
  f1 = c(f.e1[1],f.e2[1],f.e1[-1], f.e2[-1]) 
  
  if((k %% 2)==1){ 
    f = list(phi=f1/sqrt(3)) 
  } else { 
    f.e1 <- (-1)^{k/2}*cos(pi*k*x1/2)
    f.e2 <- cos(pi*k*x2/2)
    f2 = c(f.e1[1],f.e2[1],f.e1[-1],f.e2[-1]) 
    f <- list(phi=f1,psi=f2/sqrt(3/2))
  }
}
return(f)
}

4.3 Function gets.eigen.params()

Given a finite number of modes N_finite, a scaling parameter kappa, a smoothness parameter alpha, and a tadpole graph graph, function gets.eigen.params() computes EIGENVAL_ALPHA (a vector with entries \(\lambda_j^{\alpha/2}\)), EIGENVAL_MINUS_ALPHA (a vector with entries \(\lambda_j^{-\alpha/2}\)), and EIGENFUN (a matrix with columns \(e_j\) on the mesh of graph).

# Function to compute the eigenpairs of the tadpole graph
gets.eigen.params <- function(N_finite = 4, kappa = 1, alpha = 0.5, graph){
  EIGENVAL <- NULL
  EIGENVAL_ALPHA <- NULL
  EIGENVAL_MINUS_ALPHA <- NULL
  EIGENFUN <- NULL
  INDEX <- NULL
  for (j in 0:N_finite) {
    lambda_j <- kappa^2 + (j*pi/2)^2
    lambda_j_alpha_half <- lambda_j^(alpha/2)
    lambda_j_minus_alpha_half <- lambda_j^(-alpha/2)
    e_j <- tadpole.eig(j,graph)$phi
    EIGENVAL <- c(EIGENVAL, lambda_j)
    EIGENVAL_ALPHA <- c(EIGENVAL_ALPHA, lambda_j_alpha_half)  
    EIGENVAL_MINUS_ALPHA <- c(EIGENVAL_MINUS_ALPHA, lambda_j_minus_alpha_half)
    EIGENFUN <- cbind(EIGENFUN, e_j)
    INDEX <- c(INDEX, j)
    if (j>0 && (j %% 2 == 0)) {
      lambda_j <- kappa^2 + (j*pi/2)^2
      lambda_j_alpha_half <- lambda_j^(alpha/2)
      lambda_j_minus_alpha_half <- lambda_j^(-alpha/2)
      e_j <- tadpole.eig(j,graph)$psi
      EIGENVAL <- c(EIGENVAL, lambda_j)
      EIGENVAL_ALPHA <- c(EIGENVAL_ALPHA, lambda_j_alpha_half)    
      EIGENVAL_MINUS_ALPHA <- c(EIGENVAL_MINUS_ALPHA, lambda_j_minus_alpha_half)
      EIGENFUN <- cbind(EIGENFUN, e_j)
      INDEX <- c(INDEX, j+0.1)
      }
    }
  return(list(EIGENVAL = EIGENVAL,
              EIGENVAL_ALPHA = EIGENVAL_ALPHA, 
              EIGENVAL_MINUS_ALPHA = EIGENVAL_MINUS_ALPHA,
              EIGENFUN = EIGENFUN,
              INDEX = INDEX))
}

4.4 Function construct_piecewise_projection()

Given a matrix projected_U_approx with approximated values at discrete time points, a sequence of time points time_seq, and an extended sequence of time points overkill_time_seq, function construct_piecewise_projection() constructs a piecewise constant projection of the approximated values over the extended time sequence.

# Function to construct a piecewise constant projection of approximated values
construct_piecewise_projection <- function(projected_U_approx, time_seq, overkill_time_seq) {
  projected_U_piecewise <- matrix(NA, nrow = nrow(projected_U_approx), ncol = length(overkill_time_seq))
  
  # Assign value at t = 0
  projected_U_piecewise[, which(overkill_time_seq == 0)] <- projected_U_approx[, 1]
  
  # Assign values for intervals (t_{k-1}, t_k]
  for (k in 2:length(time_seq)) {
    idxs <- which(overkill_time_seq > time_seq[k - 1] & overkill_time_seq <= time_seq[k])
    projected_U_piecewise[, idxs] <- projected_U_approx[, k]
  }
  
  return(projected_U_piecewise)
}

4.5 Function compute_guiding_lines()

Given a vector x_axis_vector, a matrix errors with error values, a vector theoretical_rates with theoretical convergence rates, and a function line_equation_fun (either loglog_line_equation or exp_line_equation), function compute_guiding_lines() computes guiding lines for convergence plots.

loglog_line_equation <- function(x1, y1, slope) {
  b <- log10(y1 / (x1 ^ slope))
  
  function(x) {
    (x ^ slope) * (10 ^ b)
  }
}
exp_line_equation <- function(x1, y1, slope) {
  lnC <- log(y1) - slope * x1
  
  function(x) {
    exp(lnC + slope * x)
  }
}
compute_guiding_lines <- function(x_axis_vector, errors, theoretical_rates, line_equation_fun) {
  guiding_lines <- matrix(NA, nrow = length(x_axis_vector), ncol = length(theoretical_rates))
  
  for (j in seq_along(theoretical_rates)) {
    guiding_lines_aux <- matrix(NA, nrow = length(x_axis_vector), ncol = length(x_axis_vector))
    
    for (k in seq_along(x_axis_vector)) {
      point_x1 <- x_axis_vector[k]
      point_y1 <- errors[k, j]
      slope <- theoretical_rates[j]
      
      line <- line_equation_fun(x1 = point_x1, y1 = point_y1, slope = slope)
      guiding_lines_aux[, k] <- line(x_axis_vector)
    }
    
    guiding_lines[, j] <- rowMeans(guiding_lines_aux)
  }
  
  return(guiding_lines)
}

4.6 Functions for computing an exact solution to the fractional diffusion equation

Below we present closed-form expressions for \(\displaystyle G_j(t)= \int_0^t e^{\lambda^{\alpha/2}_jr}g(r)dr\) corresponding to some choices of \(g(r)\).

\[\begin{aligned} g(r) &= Ae^{-\lambda^{\alpha/2}_j r} &\implies\quad G_j(t) &= At, \quad A\in\mathbb{R} \\[1.5ex] g(r) &= Ae^{\mu r} &\implies\quad G_j(t) &= A \frac{e^{(\lambda^{\alpha/2}_j+\mu)t} - 1}{\lambda^{\alpha/2}_j + \mu}, \quad -\lambda^{\alpha/2}_j \ne \mu \in \mathbb{R} \\[1.5ex] g(r) &= Ar^n &\implies\quad G_j(t) &= A\frac{(-1)^{n+1} n!}{(\lambda_j^{\alpha/2})^{n+1}} \left(1 - e^{\lambda_j^{\alpha/2} t} \sum_{k=0}^n \frac{(-\lambda_j^{\alpha/2} t)^k}{k!} \right), \quad n=0,1,\dots \\[1.5ex] g(r) &= A\sin(\omega r) &\implies\quad G_j(t) &= A \frac{e^{\lambda_j^{\alpha/2} t} \left( \lambda_j^{\alpha/2} \sin(\omega t) - \omega \cos(\omega t) \right) + \omega}{(\lambda_j^{\alpha/2})^2 + \omega^2}, \quad \omega \in \mathbb{R} \\[1.5ex] g(r) &= A\cos(\theta r) &\implies\quad G_j(t) &= A \frac{e^{\lambda_j^{\alpha/2} t} \left( \lambda_j^{\alpha/2} \cos(\theta t) + \theta \sin(\theta t) \right) - \lambda_j^{\alpha/2}}{(\lambda_j^{\alpha/2})^2 + \theta^2}, \quad \theta \in \mathbb{R} \end{aligned}\]
# Functions to compute the exact solution to the fractional diffusion equation
g_linear <- function(r, A, lambda_j_alpha_half) {
  return(A * exp(-lambda_j_alpha_half * r))
  }
G_linear <- function(t, A) {
  return(A * t)
  }
g_exp <- function(r, A, mu) {
  return(A * exp(mu * r))
  }
G_exp <- function(t, A, lambda_j_alpha_half, mu) {
  exponent <- lambda_j_alpha_half + mu
  return(A * (exp(exponent * t) - 1) / exponent)
  }
g_poly <- function(r, A, n) {
  return(A * r^n)
}
G_poly <- function(t, A, lambda_j_alpha_half, n) {
  t <- as.vector(t)
  k_vals <- 0:n
  sum_term <- sapply(t, function(tt) {
    sum(((-lambda_j_alpha_half * tt)^k_vals) / factorial(k_vals))
  })
  coeff <- ((-1)^(n + 1)) * factorial(n) / (lambda_j_alpha_half^(n + 1))
  return(A * coeff * (1 - exp(lambda_j_alpha_half * t) * sum_term))
}
g_sin <- function(r, A, omega) {
  return(A * sin(omega * r))
}
G_sin <- function(t, A, lambda_j_alpha_half, omega) {
  denom <- lambda_j_alpha_half^2 + omega^2
  numerator <- exp(lambda_j_alpha_half * t) * (lambda_j_alpha_half * sin(omega * t) - omega * cos(omega * t)) + omega
  return(A * numerator / denom)
}
g_cos <- function(r, A, theta) {
  return(A * cos(theta * r)) 
}
G_cos <- function(t, A, lambda_j_alpha_half, theta) {
  denom <- lambda_j_alpha_half^2 + theta^2
  numerator <- exp(lambda_j_alpha_half * t) * (lambda_j_alpha_half * cos(theta * t) + theta * sin(theta * t)) - lambda_j_alpha_half
  return(A * numerator / denom)
}

4.7 Function reversecolumns()

Given a matrix mat, function reversecolumns() reverses the order of its columns.

reversecolumns <- function(mat) {
  return(mat[, rev(seq_len(ncol(mat)))])
}

5 For Euclidean domains (rectangle)

gets.mesh.and.FEM.on.rectangle <- function(a, b, n) {
  mesh <- fmesher::fm_rcdt_2d(
  lattice = fmesher::fm_lattice_2d(
  x = seq(0, a, length.out = n + 1),
  y = seq(0, b, length.out = n + 1)
), extend = FALSE)
  FEM <- fmesher::fm_fem(mesh, order = 1)
  return(list(mesh = mesh, 
              Cl = FEM$c0,
              C = FEM$c1,
              G = FEM$g1))
}
compute_true_eigen_rectangle <- function(a = 1,
                                         b = 1,
                                         loc,
                                         kappa = 1, 
                                         alpha = 1,
                                         m_max = 3, 
                                         n_max = 3) {
  loc_x <- loc[,1]
  loc_y <- loc[,2]
  N <- (m_max+1)*(n_max+1)
  # Prepare lists
  EIGENVAL <- numeric(N)
  m_vector <- numeric(N)
  n_vector <- numeric(N)
  EIGENFUN <- matrix(0, nrow = nrow(loc), ncol = N)
  
  i <- 0 
  # Loop over mode indices
  for (m in 0:m_max) {
    for (n in 0:n_max) {
      lambda_mn <- kappa^2 + pi^2 * ((m^2 / a^2) + (n^2 / b^2))
      EIGENVAL[i + 1] <- lambda_mn
      # Evaluate eigenfunction on mesh grid
      phi_mn <-  cos(m*pi*loc_x/a) * cos(n*pi*loc_y/b)
      EIGENFUN[, i + 1] <- phi_mn
      m_vector[i + 1] <- m
      n_vector[i + 1] <- n
      i <- i + 1
    }
  }
  # Sort eigenvalues and corresponding eigenfunctions
  idx <- order(EIGENVAL)
  EIGENVAL <- EIGENVAL[idx]
  EIGENVAL_ALPHA <- EIGENVAL^(alpha/2)
  EIGENFUN <- EIGENFUN[, idx]
  m_vector <- m_vector[idx]
  n_vector <- n_vector[idx]
  
  return(list(EIGENVAL = EIGENVAL,
              EIGENVAL_ALPHA = EIGENVAL_ALPHA,
              EIGENFUN = EIGENFUN,
              m_vector = m_vector,
              n_vector = n_vector))
}

6 For manifolds (sphere)

# Function to get globe from h using spline
globe_from_h <- function(h) {
  readed_data <- readRDS(
    file = here::here("data_files/h_min_max_vs_globe.RDS")
  )
  
  globe_vector <- readed_data$globe_vector
  h_min_vector <- readed_data$h_min_vector
  
  ord <- order(h_min_vector)
  h_sorted <- h_min_vector[ord]
  globe_sorted <- globe_vector[ord]
  
  # Create a smooth spline function: globe as function of h_max
  spline_func <- splinefun(x = h_sorted, 
                           y = globe_sorted, 
                           method = "monoH.FC")

  return(round(spline_func(h)))
}
calculate_laplace_beltrami_eigenvalues <- function(kappa = 0, L_max = 5, rot.inv = FALSE) {
  
  eigenvalues <- numeric(0)
  
  for (l in 0:L_max) {
    lambda_l <- kappa^2 + l * (l + 1)
    
    if (rot.inv) {
      # Only one eigenvalue per l (rotational invariance)
      eigenvalues <- c(eigenvalues, lambda_l)
    } else {
      # Full multiplicity: (m = -l,...,l)
      multiplicity <- 2 * l + 1
      eigenvalues <- c(eigenvalues, rep(lambda_l, multiplicity))
    }
  }
  
  return(eigenvalues)
}


compute_true_eigen_sphere <- function(mesh, 
                                      kappa,
                                      alpha,
                                      L_max,
                                      rot.inv){
  EIGENFUN <- fmesher::fm_raw_basis(
    mesh = mesh, 
    type = "sph.harm",
    n = L_max, 
    rot.inv = rot.inv)
  EIGENVAL <- calculate_laplace_beltrami_eigenvalues(
    kappa = kappa, 
    L_max = L_max, 
    rot.inv = rot.inv)
  idx <- order(EIGENVAL)
  EIGENVAL <- EIGENVAL[idx]
  EIGENVAL_ALPHA <- EIGENVAL^(alpha/2)
  EIGENFUN <- EIGENFUN[, idx]
  return(list(EIGENVAL = EIGENVAL,
              EIGENVAL_ALPHA = EIGENVAL_ALPHA,
              EIGENFUN = EIGENFUN))
}

7 Plotting functions

7.1 Function plotting.order()

Given a vector v and a graph object graph, function plotting.order() orders the mesh values for plotting.

# Function to order the vertices for plotting
plotting.order <- function(v, graph){
  edge_number <- graph$mesh$VtE[, 1]
  pos <- sum(edge_number == 1)+1
  return(c(v[1], v[3:pos], v[2], v[(pos+1):length(v)], v[2]))
}

7.2 Function global.scene.setter()

Given ranges for the x, y, and z axes, and an optional aspect ratio for the z axis, function global.scene.setter() sets the scene for 3D plots so that all plots have the same aspect ratio and camera position.

# Function to set the scene for 3D plots
global.scene.setter <- function(x_range, y_range, z_range, z_aspectratio = 4) {
  
  return(list(xaxis = list(title = "x", range = x_range),
              yaxis = list(title = "y", range = y_range),
              zaxis = list(title = "z", range = z_range),
              aspectratio = list(x = 2*(1+2/pi), 
                                 y = 2*(2/pi), 
                                 z = z_aspectratio*(2/pi)),
              camera = list(eye = list(x = (1+2/pi)/2, 
                                       y = 4, 
                                       z = 2),
                            center = list(x = (1+2/pi)/2, 
                                          y = 0, 
                                          z = 0))))
}
global.scene.setter.rec.and.sphere <- function(x_range, y_range, z_range) {
  
  return(list(xaxis = list(title = "x", range = x_range),
              yaxis = list(title = "y", range = y_range),
              zaxis = list(title = "z", range = z_range),
              aspectratio = list(x = 1, 
                                 y = 1, 
                                 z = 1),
              camera = list(eye = list(x = 2, 
                                       y = 2, 
                                       z = 2),
                            center = list(x = 0, 
                                          y = 0, 
                                          z = 0))))
}


global.scene.setter.rec.and.sphere.for.hat <- function(x_range, y_range, z_range) {
  
  return(list(xaxis = list(title = "x", range = x_range),
              yaxis = list(title = "y", range = y_range),
              zaxis = list(title = "z", range = z_range),
              aspectratio = list(x = 1, 
                                 y = 1, 
                                 z = 0.4),
              camera = list(eye = list(x = 2, 
                                       y = 2, 
                                       z = 2),
                            center = list(x = 0, 
                                          y = 0, 
                                          z = 0))))
}

7.3 Function graph.plotter.3d()

Given a graph object graph, a sequence of time points time_seq, and one or more matrices ... representing function values defined on the mesh of graph at each time in time_seq, the graph.plotter.3d() function generates an interactive 3D visualization of these values over time.

# Function to plot in 3D
graph.plotter.3d.old <- function(graph, time_seq, frame_val_to_display, ...) {
  U_list <- list(...)
  U_names <- sapply(substitute(list(...))[-1], deparse)

  # Spatial coordinates
  x <- plotting.order(graph$mesh$V[, 1], graph)
  y <- plotting.order(graph$mesh$V[, 2], graph)
  weights <- graph$mesh$weights

  # Apply plotting.order to each U
  U_list <- lapply(U_list, function(U) apply(U, 2, plotting.order, graph = graph))
  n_vars <- length(U_list)

  # Create plot_data frame with time and position replicated
  n_time <- ncol(U_list[[1]])
  base_data <- data.frame(
    x = rep(x, times = n_time),
    y = rep(y, times = n_time),
    the_graph = 0,
    frame = rep(time_seq, each = length(x))
  )

  # Add U columns to plot_data
  for (i in seq_along(U_list)) {
    base_data[[paste0("u", i)]] <- as.vector(U_list[[i]])
  }

  plot_data <- base_data

  # Generate vertical lines
  vertical_lines_list <- lapply(seq_along(U_list), function(i) {
    do.call(rbind, lapply(time_seq, function(t) {
      idx <- which(plot_data$frame == t)
      z_vals <- plot_data[[paste0("u", i)]][idx]
      data.frame(
        x = rep(plot_data$x[idx], each = 3),
        y = rep(plot_data$y[idx], each = 3),
        z = as.vector(t(cbind(0, z_vals, NA))),
        frame = rep(t, each = length(idx) * 3)
      )
    }))
  })

  # Set axis ranges
  z_range <- range(unlist(U_list))
  x_range <- range(x)
  y_range <- range(y)

  # Create plot
  p <- plot_ly(plot_data, frame = ~frame) %>%
    add_trace(x = ~x, y = ~y, z = ~the_graph, type = "scatter3d", mode = "lines",
              name = "", showlegend = FALSE,
              line = list(color = "black", width = 3))

  # Add traces for each variable
  colors <- rev(viridisLite::viridis(n_vars)) #RColorBrewer::brewer.pal(min(n_vars, 8), "Set1")
  for (i in seq_along(U_list)) {
    p <- add_trace(p,
      x = ~x, y = ~y, z = as.formula(paste0("~u", i)),
      type = "scatter3d", mode = "lines", name = U_names[i],
      line = list(color = colors[i], width = 3))
  }

  # Add vertical lines
  for (i in seq_along(vertical_lines_list)) {
    p <- add_trace(p,
      data = vertical_lines_list[[i]],
      x = ~x, y = ~y, z = ~z, frame = ~frame,
      type = "scatter3d", mode = "lines",
      line = list(color = "gray", width = 0.5),
      name = "Vertical lines",
      showlegend = FALSE)
  }
  frame_name <- deparse(substitute(frame_val_to_display))
  # Layout and animation controls
  p <- p %>%
    layout(
      scene = global.scene.setter(x_range, y_range, z_range),
      updatemenus = list(list(type = "buttons", showactive = FALSE,
                              buttons = list(
                                list(label = "Play", method = "animate",
                                     args = list(NULL, list(frame = list(duration = 2000 / length(time_seq), redraw = TRUE), fromcurrent = TRUE))),
                                list(label = "Pause", method = "animate",
                                     args = list(NULL, list(mode = "immediate", frame = list(duration = 0), redraw = FALSE)))
                              )
      )),
      title = paste0(frame_name,": ", formatC(frame_val_to_display[1], format = "f", digits = 4))
    ) %>%
    plotly_build()

  for (i in seq_along(p$x$frames)) {
    p$x$frames[[i]]$layout <- list(title = paste0(frame_name,": ", formatC(frame_val_to_display[i], format = "f", digits = 4)))
  }

  return(p)
}

7.4 Function graph.plotter.3d()

Given a graph object graph, a sequence of time points time_seq, a vector frame_val_to_display with values to display at each frame, and a list U_list representing function values defined on the mesh of graph at each time in time_seq, the graph.plotter.3d() function generates an interactive 3D visualization of these functions over time.

graph.plotter.3d <- function(graph, time_seq, frame_val_to_display, U_list) {
  U_names <- names(U_list) 
  # Spatial coordinates
  x <- plotting.order(graph$mesh$V[, 1], graph)
  y <- plotting.order(graph$mesh$V[, 2], graph)
  weights <- graph$mesh$weights

  # Apply plotting.order to each U
  U_list <- lapply(U_list, function(U) apply(U, 2, plotting.order, graph = graph))
  n_vars <- length(U_list)
  
  # Create plot_data frame with time and position replicated
  n_time <- ncol(U_list[[1]])
  base_data <- data.frame(
    x = rep(x, times = n_time),
    y = rep(y, times = n_time),
    the_graph = 0,
    frame = rep(time_seq, each = length(x))
  )

  # Add U columns to plot_data
  for (i in seq_along(U_list)) {
    base_data[[paste0("u", i)]] <- as.vector(U_list[[i]])
  }

  plot_data <- base_data

  # Generate vertical lines
  vertical_lines_list <- lapply(seq_along(U_list), function(i) {
    do.call(rbind, lapply(time_seq, function(t) {
      idx <- which(plot_data$frame == t)
      z_vals <- plot_data[[paste0("u", i)]][idx]
      data.frame(
        x = rep(plot_data$x[idx], each = 3),
        y = rep(plot_data$y[idx], each = 3),
        z = as.vector(t(cbind(0, z_vals, NA))),
        frame = rep(t, each = length(idx) * 3)
      )
    }))
  })

  # Set axis ranges
  z_range <- range(unlist(U_list))
  x_range <- range(x)
  y_range <- range(y)

  # Create plot
  p <- plot_ly(plot_data, frame = ~frame) %>%
    add_trace(x = ~x, y = ~y, z = ~the_graph, type = "scatter3d", mode = "lines",
              name = "", showlegend = FALSE,
              line = list(color = "black", width = 3))

  if (n_vars == 2) {
    colors <- RColorBrewer::brewer.pal(min(n_vars, 8), "Set1") 
    } else {
    colors <- rev(viridisLite::viridis(n_vars)) 
  }
  # RColorBrewer::brewer.pal(min(n_vars, 8), "Set1")
  for (i in seq_along(U_list)) {
    p <- add_trace(p,
      x = ~x, y = ~y, z = as.formula(paste0("~u", i)),
      type = "scatter3d", mode = "lines", name = U_names[i],
      line = list(color = colors[i], width = 3))
  }

  # Add vertical lines
  for (i in seq_along(vertical_lines_list)) {
    p <- add_trace(p,
      data = vertical_lines_list[[i]],
      x = ~x, y = ~y, z = ~z, frame = ~frame,
      type = "scatter3d", mode = "lines",
      line = list(color = "gray", width = 0.5),
      name = "Vertical lines",
      showlegend = FALSE)
  }
  frame_name <- deparse(substitute(frame_val_to_display))
  # Layout and animation controls
  p <- p %>%
    layout(
      scene = global.scene.setter(x_range, y_range, z_range),
      updatemenus = list(list(type = "buttons", showactive = FALSE,
                              buttons = list(
                                list(label = "Play", method = "animate",
                                     args = list(NULL, list(frame = list(duration = 2000 / length(time_seq), redraw = TRUE), fromcurrent = TRUE))),
                                list(label = "Pause", method = "animate",
                                     args = list(NULL, list(mode = "immediate", frame = list(duration = 0), redraw = FALSE)))
                              )
      )),
      title = paste0(frame_name,": ", formatC(frame_val_to_display[1], format = "f", digits = 4))
    ) %>%
    plotly_build()

  for (i in seq_along(p$x$frames)) {
    p$x$frames[[i]]$layout <- list(title = paste0(frame_name,": ", formatC(frame_val_to_display[i], format = "f", digits = 4)))
  }

  return(p)
}

7.5 Function error.at.each.time.plotter()

Given a graph object graph, a matrix U_true of true values, a matrix U_approx of approximated values, a sequence of time points time_seq, and a time step time_step, function error.at.each.time.plotter() computes the error at each time step and generates a plot showing the error over time.

# Function to plot the error at each time step
error.at.each.time.plotter <- function(graph, U_true, U_approx, time_seq, time_step) {
  weights <- graph$mesh$weights
  error_at_each_time <- t(weights) %*% (U_true - U_approx)^2
  error <- sqrt(as.double(t(weights) %*% (U_true - U_approx)^2 %*% rep(time_step, ncol(U_true))))
  p <- plot_ly() %>% 
  add_trace(
  x = ~time_seq, y = ~error_at_each_time, type = 'scatter', mode = 'lines+markers',
  line = list(color = 'blue', width = 2),
  marker = list(color = 'blue', size = 4),
  name = "",
  showlegend = TRUE
) %>% 
  layout(
  title = paste0("Error at Each Time Step (Total error = ", formatC(error, format = "f", digits = 9), ")"),
  xaxis = list(title = "t"),
  yaxis = list(title = "Error"),
  legend = list(x = 0.1, y = 0.9)
)
  return(p)
}

7.6 Function graph.plotter.3d.comparer()

Given a graph object graph, matrices U_true and U_approx representing true and approximated values, and a sequence of time points time_seq, function graph.plotter.3d.comparer() generates a 3D plot comparing the true and approximated values over time, with color-coded traces for each time point.

# Function to plot the 3D comparison of U_true and U_approx
graph.plotter.3d.comparer <- function(graph, U_true, U_approx, time_seq) {
  x <- graph$mesh$V[, 1]; y <- graph$mesh$V[, 2]
  x <- plotting.order(x, graph); y <- plotting.order(y, graph)

  U_true <- apply(U_true, 2, plotting.order, graph = graph)
  U_approx <- apply(U_approx, 2, plotting.order, graph = graph)
  n_times <- length(time_seq)
  
  x_range <- range(x); y_range <- range(y); z_range <- range(c(U_true, U_approx))
  
  # Normalize time_seq
  time_normalized <- (time_seq - min(time_seq)) / (max(time_seq) - min(time_seq))
  blues <- colorRampPalette(c("lightblue", "blue"))(n_times)
  reds <- colorRampPalette(c("mistyrose", "red"))(n_times)
  
  # Accurate colorscales
  colorscale_greens <- Map(function(t, col) list(t, col), time_normalized, blues)
  colorscale_reds <- Map(function(t, col) list(t, col), time_normalized, reds)
  
  p <- plot_ly()
  
  # Static black graph structure
  p <- p %>%
    add_trace(x = x, y = y, z = rep(0, length(x)),
              type = "scatter3d", mode = "lines",
              line = list(color = "black", width = 4),
              name = "Graph", showlegend = FALSE)
  
  # U_true traces (green)
  for (i in seq_len(n_times)) {
    z <- U_true[, i]
    p <- add_trace(
      p,
      type = "scatter3d",
      mode = "lines",
      x = x, y = y, z = z,
      line = list(color = blues[i], width = 4),
      showlegend = FALSE,
      scene = "scene"
    )
  }
  
  # U_approx traces (dashed red)
  for (i in seq_len(n_times)) {
    z <- U_approx[, i]
    p <- add_trace(
      p,
      type = "scatter3d",
      mode = "lines",
      x = x, y = y, z = z,
      line = list(color = reds[i], width = 4, dash = "dot"),
      showlegend = FALSE,
      scene = "scene"
    )
  }
  
  # Dummy green colorbar (True) – with ticks
  p <- add_trace(
    p,
    type = "heatmap",
    z = matrix(time_seq, nrow = 1),
    showscale = TRUE,
    colorscale = colorscale_greens,
    colorbar = list(
      title = list(font = list(size = 12, color = "black"), text = "Time", side = "top"),
      len = 0.9,
      thickness = 15,
      x = 1.02,
      xanchor = "left",
      y = 0.5,
      yanchor = "middle",
      tickvals = NULL,   # hide tick values
      ticktext = NULL,
      ticks = ""         # also hides tick marks
    ),
    x = matrix(time_seq, nrow = 1),
    y = matrix(1, nrow = 1),
    hoverinfo = "skip",
    opacity = 0
  )

# Dummy red colorbar (Approx) – no ticks
  p <- add_trace(
    p,
    type = "heatmap",
    z = matrix(time_seq, nrow = 1),
    showscale = TRUE,
    colorscale = colorscale_reds,
    colorbar = list(
      title = list(font = list(size = 12, color = "black"), text = ".", side = "top"),
      len = 0.9,
      thickness = 15,
      x = 1.05,
      xanchor = "left",
      y = 0.5,
      yanchor = "middle"
    ),
    x = matrix(time_seq, nrow = 1),
    y = matrix(1, nrow = 1),
    hoverinfo = "skip",
    opacity = 0
  )
  p <- p %>%
    add_trace(x = x, y = y, z = rep(0, length(x)),
              type = "scatter3d", mode = "lines",
              line = list(color = "black", width = 4),
              name = "Graph", showlegend = FALSE)
  p <- layout(p,
            scene = global.scene.setter(x_range, y_range, z_range),
            xaxis = list(visible = FALSE),
            yaxis = list(visible = FALSE),
            annotations = list(
  list(
    text = "Exact",
    x = 1.045,
    y = 0.5,
    xref = "paper",
    yref = "paper",
    showarrow = FALSE,
    font = list(size = 12, color = "black"),
    textangle = -90
  ),
  list(
    text = "Approx",
    x = 1.075,
    y = 0.5,
    xref = "paper",
    yref = "paper",
    showarrow = FALSE,
    font = list(size = 12, color = "black"),
    textangle = -90
  )
)

)

  
  return(p)
}

7.7 Function graph.plotter.3d.single()

Given a graph object graph, a matrix U_true representing true values, and a sequence of time points time_seq, function graph.plotter.3d.single() generates a 3D plot of the true values over time, with color-coded traces for each time point.

# Function to plot a single 3D line for 
graph.plotter.3d.single <- function(graph, U_true, time_seq) {
  x <- graph$mesh$V[, 1]; y <- graph$mesh$V[, 2]
  x <- plotting.order(x, graph); y <- plotting.order(y, graph)

  U_true <- apply(U_true, 2, plotting.order, graph = graph)
  n_times <- length(time_seq)
  
  x_range <- range(x); y_range <- range(y); z_range <- range(U_true)
  z_range[1] <- z_range[1] - 10^-6
  viridis_colors <- viridisLite::viridis(100)
  
  # Normalize time_seq
  time_normalized <- (time_seq - min(time_seq)) / (max(time_seq) - min(time_seq))
  #greens <- colorRampPalette(c("palegreen", "darkgreen"))(n_times)
  greens <- colorRampPalette(c(viridis_colors[1], viridis_colors[50],  viridis_colors[100]))(n_times)
  # Accurate colorscales
  colorscale_greens <- Map(function(t, col) list(t, col), time_normalized, greens)
  
  p <- plot_ly()
  
  # Add the 3D lines with fading green color
  for (i in seq_len(n_times)) {
    z <- U_true[, i]
    
    p <- add_trace(
      p,
      type = "scatter3d",
      mode = "lines",
      x = x,
      y = y,
      z = z,
      line = list(color = greens[i], width = 2),
      showlegend = FALSE,
      scene = "scene"
    )
  }
  p <- p %>%
    add_trace(x = x, y = y, z = rep(0, length(x)),
              type = "scatter3d", mode = "lines",
              line = list(color = "black", width = 5),
              name = "Graph", showlegend = FALSE)
  # Add dummy heatmap to show colorbar (not part of scene)
  p <- add_trace(
    p,
    type = "heatmap",
    z = matrix(time_seq, nrow = 1),
    showscale = TRUE,
    colorscale = colorscale_greens,
    colorbar = list(
    title = list(font = list(size = 12, color = "black"), text = "Time", side = "top"),
    len = 0.9,         # height (0 to 1)
    thickness = 15,     # width in pixels
    x = 1.02,           # shift it slightly right of the plot
    xanchor = "left",
    y = 0.5,
    yanchor = "middle"),
    x = matrix(time_seq, nrow = 1),
    y = matrix(1, nrow = 1),
    hoverinfo = "skip",
    opacity = 0
  )
  
  p <- layout(p,
              scene = global.scene.setter(x_range, y_range, z_range),
              xaxis = list(visible = FALSE),
              yaxis = list(visible = FALSE)
  )
  
  return(p)
}

7.8 Function error.convergence.plotter()

Given a vector x_axis_vector, a vector alpha_vector of parameter values, a matrix errors with error values, vectors theoretical_rates and observed_rates with convergence rates, a function line_equation_fun (either loglog_line_equation or exp_line_equation), and plot titles and labels, function error.convergence.plotter() generates a convergence plot showing errors and guiding lines.

# Function to plot the error convergence
error.convergence.plotter <- function(x_axis_vector, 
                                      alpha_vector, 
                                      errors, 
                                      theoretical_rates, 
                                      observed_rates,
                                      line_equation_fun,
                                      fig_title,
                                      x_axis_label,
                                      apply_sqrt = FALSE) {
  
  x_vec <- if (apply_sqrt) sqrt(x_axis_vector) else x_axis_vector
  
  guiding_lines <- compute_guiding_lines(x_axis_vector = x_vec, 
                                         errors = errors, 
                                         theoretical_rates = theoretical_rates, 
                                         line_equation_fun = line_equation_fun)
  
  default_colors <- scales::hue_pal()(length(alpha_vector))
  
  plot_lines <- lapply(1:ncol(guiding_lines), function(i) {
    geom_line(
      data = data.frame(x = x_vec, y = guiding_lines[, i]),
      aes(x = x, y = y),
      color = default_colors[i],
      linetype = "dashed",
      show.legend = FALSE
    )
  })
  
  df <- as.data.frame(cbind(x_vec, errors))
  colnames(df) <- c("x_axis_vector", alpha_vector)
  df_melted <- melt(df, id.vars = "x_axis_vector", variable.name = "column", value.name = "value")
  
  custom_labels <- paste0(formatC(alpha_vector, format = "f", digits = 2), 
                          " | ", 
                          formatC(theoretical_rates, format = "f", digits = 4), 
                          " | ", 
                          formatC(observed_rates, format = "f", digits = 4))
  
  df_melted$column <- factor(df_melted$column, levels = alpha_vector, labels = custom_labels)

  p <- ggplot() +
    geom_line(data = df_melted, aes(x = x_axis_vector, y = value, color = column)) +
    geom_point(data = df_melted, aes(x = x_axis_vector, y = value, color = column)) +
    plot_lines +
    labs(
      title = fig_title,
      x = x_axis_label,
      y = expression(Error),
      color = "          α  | theo  | obs"
    ) +
    (if (apply_sqrt) {
      scale_x_continuous(breaks = x_vec, labels = round(x_axis_vector, 4))
    } else {
      scale_x_log10(breaks = x_axis_vector, labels = round(x_axis_vector, 4))
    }) +
    (if (apply_sqrt) {
      scale_y_continuous(trans = "log", labels = scales::scientific_format())
    } else {
      scale_y_log10(labels = scales::scientific_format())
    }) +
    theme_minimal() +
    theme(text = element_text(family = "Palatino"),
          legend.position = "bottom",
          legend.direction = "vertical",
          plot.margin = margin(0, 0, 0, 0),
          plot.title = element_text(hjust = 0.5, size = 18, face = "bold"))
  
  return(p)
}

7.9 Function graph.plotter.3d.static()

Given a graph object graph and a list z_list of function values defined on the mesh of graph, function graph.plotter.3d.static() generates a static 3D plot of these values.

graph.plotter.3d.static <- function(graph, z_list) {
  x <- plotting.order(graph$mesh$V[, 1], graph)
  y <- plotting.order(graph$mesh$V[, 2], graph)
  U_names <- names(z_list)
  n_vars <- length(z_list)
  z_list <- lapply(z_list, function(z) plotting.order(z, graph))

  # Axis ranges
  z_range <- range(unlist(z_list))
  x_range <- range(x)
  y_range <- range(y)

  if (n_vars == 2) {
    colors <- RColorBrewer::brewer.pal(min(n_vars, 8), "Set1") 
    } else {
    colors <- rev(viridisLite::viridis(n_vars)) 
  }
  p <- plot_ly()

  for (i in seq_along(z_list)) {
    z <- z_list[[i]]

    # Main 3D curve
    p <- add_trace(
      p,
      x = x, y = y, z = z,
      type = "scatter3d", mode = "lines",
      line = list(color = colors[i], width = 3),
      name = U_names[i], showlegend = TRUE
    )

    # Efficient vertical lines: one trace with breaks (NA)
    x_vert <- rep(x, each = 3)
    y_vert <- rep(y, each = 3)
    z_vert <- unlist(lapply(z, function(zj) c(0, zj, NA)))

    p <- add_trace(
      p,
      x = x_vert, y = y_vert, z = z_vert,
      type = "scatter3d", mode = "lines",
      line = list(color = "gray", width = 0.5),
      showlegend = FALSE
    )
  }
  p <- p %>% add_trace(x = x, y = y, z = x*0, type = "scatter3d", mode = "lines",
              line = list(color = "black", width = 3),
              name = "thegraph", showlegend = FALSE) %>%
    layout(scene = global.scene.setter(x_range, y_range, z_range))
  return(p)
}

7.10 Function graph.plotter.3d.two.meshes.time()

Given two graph objects graph_finer and graph_coarser, sequences of time points time_seq and frame_val_to_display, and lists fs_finer and fs_coarser of function values defined on the meshes of graph_finer and graph_coarser respectively, function graph.plotter.3d.two.meshes.time() generates an interactive 3D visualization comparing these functions over time.

graph.plotter.3d.two.meshes.time <- function(graph_finer, graph_coarser, 
                                             time_seq, frame_val_to_display,
                                             fs_finer = list(), fs_coarser = list()) {
  # Spatial coordinates (ordered for plotting)
  x_finer <- plotting.order(graph_finer$mesh$V[, 1], graph_finer)
  y_finer <- plotting.order(graph_finer$mesh$V[, 2], graph_finer)
  x_coarser <- plotting.order(graph_coarser$mesh$V[, 1], graph_coarser)
  y_coarser <- plotting.order(graph_coarser$mesh$V[, 2], graph_coarser)
  
  n_time <- if (length(fs_finer) > 0) ncol(fs_finer[[1]]) else ncol(fs_coarser[[1]])

  # Helper: make dataframe from one function
  make_df <- function(f_mat, graph, x, y, mesh_name) {
    z <- apply(f_mat, 2, plotting.order, graph = graph)
    data.frame(
      x = rep(x, times = n_time),
      y = rep(y, times = n_time),
      z = as.vector(z),
      frame = rep(time_seq, each = length(x)),
      mesh = mesh_name
    )
  }
  
  # Build data for finer functions
  data_finer_list <- lapply(names(fs_finer), function(nm) {
    make_df(fs_finer[[nm]], graph_finer, x_finer, y_finer, nm)
  })
  
  # Build data for coarser functions
  data_coarser_list <- lapply(names(fs_coarser), function(nm) {
    make_df(fs_coarser[[nm]], graph_coarser, x_coarser, y_coarser, nm)
  })
  
  # Combine
  all_data <- c(data_finer_list, data_coarser_list)
  
  # Baseline graph (on finer mesh for consistency)
  data_graph <- data.frame(
    x = rep(x_finer, times = n_time),
    y = rep(y_finer, times = n_time),
    z = 0,
    frame = rep(time_seq, each = length(x_finer)),
    mesh = "Graph"
  )
  
# --------- Vertical lines helper ----------
vertical_lines <- function(x, y, z, frame_vals, mesh_name) {
  do.call(rbind, lapply(seq_along(frame_vals), function(i) {
    idx <- ((i - 1) * length(x) + 1):(i * length(x))
    data.frame(
      x = rep(x, each = 3),
      y = rep(y, each = 3),
      z = as.vector(t(cbind(0, z[idx], NA))),
      frame = rep(frame_vals[i], each = length(x) * 3),
      mesh = mesh_name
    )
  }))
}

# --------- Compute vertical lines per mesh using max absolute value ---------
make_vertical_from_list <- function(data_list, x, y, mesh_name) {
  if (length(data_list) == 0) return(NULL)
  
  # Reshape each function's z back to matrix: (nodes × time)
  z_mats <- lapply(data_list, function(df) {
    matrix(df$z, nrow = length(x), ncol = length(time_seq))
  })
  
  # Stack into 3D array: (nodes × time × functions)
  arr <- array(unlist(z_mats), dim = c(length(x), length(time_seq), length(z_mats)))
  
  # For each node × time, select the entry with largest absolute value (keep sign)
  idx <- apply(arr, c(1, 2), function(v) which.max(abs(v)))
  z_signed_max <- mapply(function(i, j) arr[i, j, idx[i, j]],
                         rep(1:length(x), times = length(time_seq)),
                         rep(1:length(time_seq), each = length(x)))
  
  # Flatten back into long vector
  z_signed_max <- as.vector(z_signed_max)
  
  vertical_lines(x, y, z_signed_max, time_seq, mesh_name)
}


vertical_finer   <- make_vertical_from_list(data_finer_list,   x_finer,   y_finer,   "finer")
vertical_coarser <- make_vertical_from_list(data_coarser_list, x_coarser, y_coarser, "coarser")

  
  # Compute ranges
  all_z <- unlist(lapply(all_data, function(df) df$z))
  x_range <- range(c(x_finer, x_coarser))
  y_range <- range(c(y_finer, y_coarser))
  z_range <- range(all_z)
  
  # --------- Plotly object ----------
  p <- plot_ly(frame = ~frame)
  
  # Add traces for finer + coarser (looping automatically with names)
  for (df in all_data) {
    p <- p %>%
      add_trace(data = df,
                x = ~x, y = ~y, z = ~z,
                type = "scatter3d", mode = "lines",
                line = list(width = 3),
                name = unique(df$mesh))
  }
  
  # Add baseline
  p <- p %>%
    add_trace(data = data_graph,
              x = ~x, y = ~y, z = ~z,
              type = "scatter3d", mode = "lines",
              line = list(color = "black", width = 2),
              name = "Graph", showlegend = FALSE)
  
# Add verticals (one per mesh, envelope of all functions)
if (!is.null(vertical_finer)) {
  p <- p %>%
    add_trace(data = vertical_finer,
              x = ~x, y = ~y, z = ~z,
              type = "scatter3d", mode = "lines",
              line = list(color = "gray", width = 0.5),
              name = "Vertical finer", showlegend = FALSE)
}
if (!is.null(vertical_coarser)) {
  p <- p %>%
    add_trace(data = vertical_coarser,
              x = ~x, y = ~y, z = ~z,
              type = "scatter3d", mode = "lines",
              line = list(color = "gray", width = 0.5),
              name = "Vertical coarser", showlegend = FALSE)
}

  
  frame_name <- deparse(substitute(frame_val_to_display))
  
  p <- p %>%
    layout(
      scene = global.scene.setter(x_range, y_range, z_range),
      updatemenus = list(list(
        type = "buttons", showactive = FALSE,
        buttons = list(
          list(label = "Play", method = "animate",
               args = list(NULL, list(frame = list(duration = 2000 / length(time_seq), redraw = TRUE),
                                      fromcurrent = TRUE))),
          list(label = "Pause", method = "animate",
               args = list(NULL, list(mode = "immediate", 
                                      frame = list(duration = 0), redraw = FALSE)))
        )
      )),
      title = paste0(frame_name, ": ", formatC(frame_val_to_display[1], format = "f", digits = 4))
    ) %>%
    plotly_build()
  
  # Update frame titles
  for (i in seq_along(p$x$frames)) {
    p$x$frames[[i]]$layout <- list(
      title = paste0(frame_name, ": ", formatC(frame_val_to_display[i], format = "f", digits = 4))
    )
  }
  
  return(p)
}

7.11 Function plot_3d_rectangle_scatter()

plot_3d_rectangle_scatter <- function(loc, eigvals, eigfuncs, fixed_colorscale = TRUE) {

  x <- loc[,1]
  y <- loc[,2]

  colorscale <- "Viridis"

  # Compute global color limits if fixed
  if (fixed_colorscale) {
    cmin <- min(eigfuncs)
    cmax <- max(eigfuncs)
  } else {
    cmin <- NULL
    cmax <- NULL
  }

  # Frames ----------------------------------------------------------------
  frames <- lapply(seq_len(ncol(eigfuncs)), function(i) {
    list(
      name = as.character(i),
      data = list(list(
        x = x,
        y = y,
        z = eigfuncs[,i],
        type = "scatter3d",
        mode = "markers",
        marker = list(
          size = 5,
          color = eigfuncs[,i],
          colorscale = colorscale,
          showscale = TRUE,
          cmin = cmin,
          cmax = cmax
        )
      ))
    )
  })

  # Initial Plot ----------------------------------------------------------
  p <- plot_ly(
    x = x,
    y = y,
    z = eigfuncs[,1],
    type = "scatter3d",
    mode = "markers",
    marker = list(
      size = 5,
      color = eigfuncs[,1],
      colorscale = colorscale,
      showscale = TRUE,
      cmin = cmin,
      cmax = cmax
    ),
    frame = "1"
  )

  p$x$frames <- frames

  z_range <- range(eigfuncs)
  x_range <- range(x)
  y_range <- range(y)

  frame_name <- deparse(substitute(eigvals))

  # Layout + slider -------------------------------------------------------
  p <- p %>% layout(
    title = paste0(frame_name, ": ", eigvals[1]),
    scene = global.scene.setter.rec.and.sphere(x_range, y_range, z_range),
    sliders = list(
      list(
        active = 0,
        currentvalue = list(prefix = "Frame: "),
        pad = list(t = 50),
        steps = lapply(seq_len(ncol(eigfuncs)), function(i) {
          list(
            label = as.character(i),
            method = "animate",
            args = list(
              list(as.character(i)),
              list(frame = list(duration = 300, redraw = TRUE),
                   mode = "immediate")
            )
          )
        })
      )
    ),
    updatemenus = list(
      list(
        type = "buttons",
        showactive = FALSE,
        y = 1,
        x = 1.15,
        xanchor = "right",
        yanchor = "top",
        buttons = list(
          list(label = "Play",
               method = "animate",
               args = list(
                 NULL,
                 list(frame = list(duration = 300, redraw = TRUE),
                      fromcurrent = TRUE)
               )),
          list(label = "Pause",
               method = "animate",
               args = list(NULL, list(frame = list(duration = 0))))
        )
      )
    )
  ) %>% plotly_build()

  # Update title in each frame --------------------------------------------
  for (i in seq_len(ncol(eigfuncs))) {
    p$x$frames[[i]]$layout <- list(
      title = paste0(frame_name, ": ", eigvals[i])
    )
  }

  return(p)
}

plot_3d_rectangle_onecol_vir <- function(mesh, fvals) {
  
  colorscale = "Viridis"
  opacity = 1
  
  
  x <- mesh$loc[, 1]
  y <- mesh$loc[, 2]
  tri <- mesh$graph$tv
  
  # fvals is a vector
  
  x_range <- range(x)
  y_range <- range(y)
  z_range <- range(fvals)
  
  # Static mesh3d plot
  p <- plot_ly(
    x = x, y = y, z = fvals,
    i = tri[,1] - 1,
    j = tri[,2] - 1,
    k = tri[,3] - 1,
    type = "mesh3d",
    intensity = fvals,
    colorscale = colorscale,
    opacity = opacity,
    flatshading = TRUE,
    text = paste0(
      "x: ", sprintf("%.3f", x), "<br>",
      "y: ", sprintf("%.3f", y), "<br>",
      "f: ", sprintf("%.5f", fvals)
    ),
    hoverinfo = "text"
  ) %>% layout(
    scene = global.scene.setter.rec.and.sphere(x_range, y_range, z_range)
  )
  
  return(p)
}

plot_3d_rectangle_onecol <- function(mesh, fvals) {
  
  opacity <- 1
  surface_color <- "blue"
  
  x <- mesh$loc[, 1]
  y <- mesh$loc[, 2]
  tri <- mesh$graph$tv
  
  x_range <- range(x)
  y_range <- range(y)
  z_range <- range(fvals)
  
  # Static mesh3d plot
  p <- plot_ly(
    x = x, y = y, z = fvals,
    i = tri[,1] - 1,
    j = tri[,2] - 1,
    k = tri[,3] - 1,
    type = "mesh3d",
    color = surface_color,     # Fixed blue color
    opacity = opacity,
    flatshading = TRUE,
    text = paste0(
      "x: ", sprintf("%.3f", x), "<br>",
      "y: ", sprintf("%.3f", y), "<br>",
      "f: ", sprintf("%.5f", fvals)
    ),
    hoverinfo = "text"
  ) %>% layout(
    scene = global.scene.setter.rec.and.sphere(x_range, y_range, z_range)
  )
  
  return(p)
}

plot_3d_square_mesh <- function(mesh) {
  
  x <- mesh$loc[,1]
  y <- mesh$loc[,2]
  # Flat square (or use mesh$loc[,3] if available)
  if (ncol(mesh$loc) >= 3) {
    z <- mesh$loc[,3]
  } else {
    z <- rep(0, length(x))
  }
  
  x_range <- range(x)
  y_range <- range(y)
  z_range <- c(0,1)
  
  tri <- mesh$graph$tv
  
  # ---- Uniform gray color for faces ----
  gray_rgb <- rep(list(rgb(180, 180, 180, maxColorValue = 255)), length(x))
  white_rgb <- rep(list(rgb(255, 255, 255, alpha = 255, maxColorValue = 255)), length(x))

  
  df3 <- data.frame(x = x, 
                  y = y, 
                  z = z)
  
  # ---- Plot mesh faces ----
  p <- plot_ly() %>% add_trace(
    x = x,
    y = y,
    z = z,
    i = tri[,1] - 1,
    j = tri[,2] - 1,
    k = tri[,3] - 1,
    type = "mesh3d",
    vertexcolor = white_rgb,
    flatshading = TRUE,
    opacity = 1,
    showscale = FALSE
  )
  
  # ---- Extract unique edges ----
  edges <- rbind(
    tri[,1:2],
    tri[,2:3],
    tri[,c(3,1)]
  )
  edges <- t(apply(edges, 1, sort))
  edges <- unique(edges)
  
  # ---- Efficient edges as one trace using NA breaks ----
  x_edges <- as.vector(t(cbind(x[edges[,1]], x[edges[,2]], NA)))
  y_edges <- as.vector(t(cbind(y[edges[,1]], y[edges[,2]], NA)))
  z_edges <- as.vector(t(cbind(z[edges[,1]], z[edges[,2]], NA)))
  
  p <- add_trace(
    p,
    x = x_edges, y = y_edges, z = z_edges,
    type = "scatter3d",
    mode = "lines",
    line = list(color = "black", width = 3),
    showlegend = FALSE,
    hoverinfo = "none"
  ) %>% 
    add_trace(data = df3, x = ~x, y = ~y, z = ~z, mode = "markers", type = "scatter3d", 
            marker = list(size = 4, color = "gray", symbol = 104)) %>%
    layout(
    scene = global.scene.setter.rec.and.sphere(x_range, y_range, z_range)
  )
  
  return(p)
}


plot_3d_square_mesh_with_hat <- function(mesh,
                                hat_nodes = NULL,
                                hat_height = 1,
                                hat_color = NULL,
                                hat_alpha = 0.6) {
  
  x <- mesh$loc[,1]
  y <- mesh$loc[,2]
  n <- length(x)
  
  z0 <- rep(0, n)
  tri <- mesh$graph$tv
  
  x_range <- range(x)
  y_range <- range(y)
  z_range <- c(0, hat_height)
  
  # ---- Extract unique edges once ----
  edges <- rbind(tri[,1:2], tri[,2:3], tri[,c(3,1)])
  edges <- t(apply(edges, 1, sort))
  edges <- unique(edges)
  
  # Base mesh color
  white_rgb <- rep(list(rgb(255, 255, 255, alpha = 255, maxColorValue = 255)), n)
  
  df3 <- data.frame(x = x, 
                  y = y, 
                  z = rep(0, length(x)))
  
  # ---- Base mesh ----
  p <- plot_ly() %>% add_trace(
    x = x, y = y, z = z0,
    i = tri[,1] - 1, j = tri[,2] - 1, k = tri[,3] - 1,
    type = "mesh3d",
    vertexcolor = white_rgb,
    flatshading = TRUE,
    opacity = 1,
    name = "Mesh"
  )
  
  # ---- Base mesh edges ----
  x_edges <- as.vector(t(cbind(x[edges[,1]], x[edges[,2]], NA)))
  y_edges <- as.vector(t(cbind(y[edges[,1]], y[edges[,2]], NA)))
  z_edges <- as.vector(t(cbind(z0[edges[,1]], z0[edges[,2]], NA)))
  
  # ---- Hat functions ----
  if (!is.null(hat_nodes)) {
    
    if (is.null(hat_color)) {
      hat_color <- grDevices::rainbow(length(hat_nodes))
    }
    if (length(hat_color) != length(hat_nodes)) {
      stop("length(hat_color) must equal length(hat_nodes)")
    }
    
    for (k in seq_along(hat_nodes)) {
      i0 <- hat_nodes[k]
      
      phi <- rep(0, n)
      phi[i0] <- 1
      z_hat <- hat_height * phi
      
      hat_rgb <- rep(list(adjustcolor(hat_color[k], alpha.f = hat_alpha)), n)
      
      # ---- Hat surface ----
      p <- add_trace(p,
        x = x, y = y, z = z_hat,
        i = tri[,1] - 1, j = tri[,2] - 1, k = tri[,3] - 1,
        type = "mesh3d",
        vertexcolor = hat_rgb,
        flatshading = TRUE,
        opacity = 1,
        name = paste0("phi_", i0),
        showlegend = FALSE
      )
      
      # ---- Hat edges (wireframe) ----
      x_hat_edges <- as.vector(t(cbind(x[edges[,1]], x[edges[,2]], NA)))
      y_hat_edges <- as.vector(t(cbind(y[edges[,1]], y[edges[,2]], NA)))
      z_hat_edges <- as.vector(t(cbind(z_hat[edges[,1]], z_hat[edges[,2]], NA)))
      
      p <- add_trace(p,
        x = x_hat_edges, y = y_hat_edges, z = z_hat_edges,
        type = "scatter3d", mode = "lines",
        line = list(color = hat_color[k], width = 2),
        showlegend = FALSE
      )
      
      # # Mark hat center node
      # p <- add_trace(p,
      #   x = x[i0], y = y[i0], z = z_hat[i0],
      #   type = "scatter3d", mode = "markers",
      #   marker = list(size = 8, color = hat_color[k]),
      #   name = paste0("node_", i0)
      # )
    }
  }
  
  p <- add_trace(p,
    x = x_edges, y = y_edges, z = z_edges,
    type = "scatter3d", mode = "lines",
    line = list(color = "black", width = 4),
    showlegend = FALSE
  )
  
  p <- p %>% 
    add_trace(data = df3, x = ~x, y = ~y, z = ~z, mode = "markers", type = "scatter3d", 
            marker = list(size = 4, color = "gray", symbol = 104), showlegend = FALSE) %>% 
    layout(
    scene = global.scene.setter.rec.and.sphere.for.hat(x_range, y_range, z_range)
  )
  
  return(p)
}

7.12 Function plot_3d_sphere_scatter()

plot_3d_sphere_scatter <- function(mesh, 
                                          eigvals, 
                                          eigfuncs,
                                          fixed_colorscale = TRUE) {
  
  colorscale = "Viridis"
  
  x <- mesh$loc[,1]
  y <- mesh$loc[,2]
  z <- mesh$loc[,3]
  
  # Compute global color limits if fixed
  if (fixed_colorscale) {
    cmin <- min(eigfuncs)
    cmax <- max(eigfuncs)
  } else {
    cmin <- NULL
    cmax <- NULL
  }
  
  # Create frames
  frames <- lapply(seq_len(ncol(eigfuncs)), function(i) {
    
    fvals <- eigfuncs[,i]
    
    list(
      name = as.character(i),
      data = list(list(
        x = x,
        y = y,
        z = z,
        type = "scatter3d",
        mode = "markers",
        marker = list(
          size = 5,
          color = fvals,
          colorscale = colorscale,
          showscale = TRUE,
          cmin = cmin,
          cmax = cmax
        ),
        text = paste0(
          "x: ", sprintf("%.3f", x), "<br>",
          "y: ", sprintf("%.3f", y), "<br>",
          "z: ", sprintf("%.3f", z), "<br>",
          "f: ", sprintf("%.5f", fvals)
        ),
        hoverinfo = "text"
      ))
    )
  })
  
  # Initial plot (frame 1)
  fvals0 <- eigfuncs[,1]
  
  p <- plot_ly(
    x = x, y = y, z = z,
    type = "scatter3d",
    mode = "markers",
    marker = list(
      size = 5,
      color = fvals0,
      colorscale = colorscale,
      showscale = TRUE,
      cmin = cmin,
      cmax = cmax
    ),
    text = paste0(
      "x: ", sprintf("%.3f", x), "<br>",
      "y: ", sprintf("%.3f", y), "<br>",
      "z: ", sprintf("%.3f", z), "<br>",
      "f: ", sprintf("%.5f", fvals0)
    ),
    hoverinfo = "text",
    frame = "1"
  )
  
  frame_name <- deparse(substitute(eigvals))
  
  p$x$frames <- frames
  
  x_range <- range(x)
  y_range <- range(y)
  z_range <- range(z)
  
  # Slider + play/pause
  p <- p %>% layout(
    title = paste0(frame_name, ": ", eigvals[1]),
    scene = global.scene.setter.rec.and.sphere(x_range, y_range, z_range),
    sliders = list(
      list(
        active = 0,
        currentvalue = list(prefix = "Mode: "),
        pad = list(t = 50),
        steps = lapply(seq_len(ncol(eigfuncs)), function(i) {
          list(
            label = as.character(i),
            method = "animate",
            args = list(list(as.character(i)),
                        list(mode = "immediate",
                             frame = list(duration = 300, redraw = TRUE),
                             transition = list(duration = 0)))
          )
        })
      )
    ),
    updatemenus = list(
      list(
        type = "buttons",
        showactive = FALSE,
        y = 1,
        x = 1.15,
        xanchor = "right",
        yanchor = "top",
        buttons = list(
          list(
            label = "Play",
            method = "animate",
            args = list(NULL,
                        list(frame = list(duration = 300, redraw = TRUE),
                             fromcurrent = TRUE,
                             mode = "immediate"))
          ),
          list(
            label = "Pause",
            method = "animate",
            args = list(NULL,
                        list(frame = list(duration = 0, redraw = FALSE),
                             mode = "immediate"))
          )
        )
      )
    )
  ) %>% plotly_build()
  
  # Update title per frame
  for (i in seq_len(ncol(eigfuncs))) {
    p$x$frames[[i]]$layout <- list(
      title = paste0(frame_name, ": ", eigvals[i])
    )
  }
  
  return(p)
}

plot_3d_slider_sphere <- function(mesh, eigvals, eigfuncs, fixed_colorscale = TRUE) {
  
  colorscale = "Viridis"
  opacity = 1
  
  x <- mesh$loc[, 1]
  y <- mesh$loc[, 2]
  z <- mesh$loc[, 3]
  tri <- mesh$graph$tv
  
  # Global intensity limits if fixed
  if (fixed_colorscale) {
    cmin <- min(eigfuncs)
    cmax <- max(eigfuncs)
  } else {
    cmin <- NULL
    cmax <- NULL
  }
  
  # Create frames
  frames <- lapply(seq_len(ncol(eigfuncs)), function(i) {
    fvals <- eigfuncs[,i]
    list(
      name = as.character(i),
      data = list(list(
        x = x,
        y = y,
        z = z,
        i = tri[,1] - 1,
        j = tri[,2] - 1,
        k = tri[,3] - 1,
        type = "mesh3d",
        intensity = fvals,
        colorscale = colorscale,
        opacity = opacity,
        flatshading = TRUE,
        cmin = cmin,
        cmax = cmax,
        text = paste0(
          "x: ", sprintf("%.3f", x), "<br>",
          "y: ", sprintf("%.3f", y), "<br>",
          "z: ", sprintf("%.3f", z), "<br>",
          "f: ", sprintf("%.5f", fvals)
        ),
        hoverinfo = "text"
      ))
    )
  })
  
  # Initial plot
  fvals0 <- eigfuncs[,1]
  
  p <- plot_ly(
    x = x, y = y, z = z,
    i = tri[,1] - 1,
    j = tri[,2] - 1,
    k = tri[,3] - 1,
    type = "mesh3d",
    intensity = fvals0,
    colorscale = colorscale,
    opacity = opacity,
    flatshading = TRUE,
    cmin = cmin,
    cmax = cmax,
    text = paste0(
      "x: ", sprintf("%.3f", x), "<br>",
      "y: ", sprintf("%.3f", y), "<br>",
      "z: ", sprintf("%.3f", z), "<br>",
      "f: ", sprintf("%.5f", fvals0)
    ),
    colorbar = list(
      thickness = 15,
      len = 0.5,      # fraction of the height of the plot
      y = 0.5,        # center vertically (0 = bottom, 1 = top)
      yanchor = "middle",
      title = ""
    ),
    hoverinfo = "text",
    frame = "1"
  )
  
  frame_name <- deparse(substitute(eigvals))
  
  p$x$frames <- frames
  
  # Layout + slider + buttons
  p <- p %>% layout(
    title = paste0(frame_name, ": ", eigvals[1]),
    scene = global.scene.setter.rec.and.sphere(range(x), range(y), range(z)),
    sliders = list(
      list(
        active = 0,
        currentvalue = list(prefix = "Frame: "),
        pad = list(t = 50),
        steps = lapply(seq_len(ncol(eigfuncs)), function(i) {
          list(
            label = as.character(i),
            method = "animate",
            args = list(list(as.character(i)),
                        list(mode = "immediate",
                             frame = list(duration = 300, redraw = TRUE),
                             transition = list(duration = 0)))
          )
        })
      )
    ),
    updatemenus = list(
      list(
        type = "buttons",
        showactive = FALSE,
        y = 1,
        x = 1.15,
        xanchor = "right",
        yanchor = "top",
        buttons = list(
          list(label = "Play",
               method = "animate",
               args = list(NULL, list(frame = list(duration = 300, redraw = TRUE),
                                      fromcurrent = TRUE, mode = "immediate"))),
          list(label = "Pause",
               method = "animate",
               args = list(NULL, list(frame = list(duration = 0, redraw = FALSE),
                                      mode = "immediate")))
        )
      )
    )
  ) %>% plotly_build()
  
  # Add titles for frames
  for (i in seq_len(ncol(eigfuncs))) {
    p$x$frames[[i]]$layout <- list(
      title = paste0(frame_name, ": ", eigvals[i])
    )
  }
  
  return(p)
}


plot_3d_sphere_scatter_onecol <- function(mesh, eigfuncs) {
  
  colorscale = "Viridis"
  
  x <- mesh$loc[,1]
  y <- mesh$loc[,2]
  z <- mesh$loc[,3]
  
  # eigfuncs is a vector
  fvals <- eigfuncs
  
  x_range <- range(x)
  y_range <- range(y)
  z_range <- range(z)
  
  # Build static 3D scatter plot
  p <- plot_ly(
    x = x, y = y, z = z,
    type = "scatter3d",
    mode = "markers",
    marker = list(
      size = 5,
      color = fvals,
      colorscale = colorscale,
      showscale = TRUE
    ),
    text = paste0(
      "x: ", sprintf("%.3f", x), "<br>",
      "y: ", sprintf("%.3f", y), "<br>",
      "z: ", sprintf("%.3f", z), "<br>",
      "f: ", sprintf("%.5f", fvals)
    ),
    hoverinfo = "text"
  ) %>% layout(
    scene = global.scene.setter.rec.and.sphere(x_range, y_range, z_range)
  )
  
  return(p)
}



# plot_3d_sphere_surface_onecol <- function(mesh, fvals) {
#   
#   colorscale = "Viridis"
#   opacity = 1
#   
#   
#   x <- mesh$loc[, 1]
#   y <- mesh$loc[, 2]
#   z <- mesh$loc[, 3]
#   tri <- mesh$graph$tv
#   
#   # fvals is a vector
#   
#   x_range <- range(x)
#   y_range <- range(y)
#   z_range <- range(z)
#   
#   # Static mesh3d plot
#   p <- plot_ly(
#     x = x, y = y, z = z,
#     i = tri[,1] - 1,
#     j = tri[,2] - 1,
#     k = tri[,3] - 1,
#     type = "mesh3d",
#     intensity = fvals,
#     colorscale = colorscale,
#     opacity = opacity,
#     flatshading = TRUE,
#     text = paste0(
#       "x: ", sprintf("%.3f", x), "<br>",
#       "y: ", sprintf("%.3f", y), "<br>",
#       "z: ", sprintf("%.3f", z), "<br>",
#       "f: ", sprintf("%.5f", fvals)
#     ),
#     hoverinfo = "text"
#   ) %>% layout(
#     scene = global.scene.setter.rec.and.sphere(x_range, y_range, z_range)
#   )
#   
#   return(p)
# }


plot_3d_sphere_surface_onecol <- function(mesh, fvals) {
  
  colorscale = "Viridis"
  opacity = 1
  
  x <- mesh$loc[, 1]
  y <- mesh$loc[, 2]
  z <- mesh$loc[, 3]
  tri <- mesh$graph$tv
  
  x_range <- range(x)
  y_range <- range(y)
  z_range <- range(z)
  
  # Static mesh3d plot
  p <- plot_ly(
    x = x, y = y, z = z,
    i = tri[,1] - 1,
    j = tri[,2] - 1,
    k = tri[,3] - 1,
    type = "mesh3d",
    intensity = fvals,
    colorscale = colorscale,
    opacity = opacity,
    flatshading = TRUE,
    text = paste0(
      "x: ", sprintf("%.3f", x), "<br>",
      "y: ", sprintf("%.3f", y), "<br>",
      "z: ", sprintf("%.3f", z), "<br>",
      "f: ", sprintf("%.5f", fvals)
    ),
    hoverinfo = "text",
    colorbar = list(
      thickness = 15,
      len = 0.5,      # fraction of the height of the plot
      y = 0.5,        # center vertically (0 = bottom, 1 = top)
      yanchor = "middle",
      title = ""
    )
  ) %>% layout(
    scene = global.scene.setter.rec.and.sphere(x_range, y_range, z_range)
  )
  
  return(p)
}


plot_3d_mesh_edges_faces_old <- function(mesh) {
  
  x <- mesh$loc[,1]
  y <- mesh$loc[,2]
  z <- mesh$loc[,3]
  tri <- mesh$graph$tv
  
  # ---- Uniform gray color for faces (vertexcolor) ----
  gray_rgb <- rep(list(rgb(180, 180, 180, maxColorValue = 255)), length(x))
  
  p <- plot_ly() %>% add_trace(
    x = x,
    y = y,
    z = z,
    i = tri[,1] - 1,
    j = tri[,2] - 1,
    k = tri[,3] - 1,
    type = "mesh3d",
    vertexcolor = gray_rgb,   # <- the correct way
    flatshading = TRUE,
    opacity = 1,
    showscale = FALSE
  )
  
  # ---- Extract unique edges ----
  edges <- rbind(
    tri[,1:2],
    tri[,2:3],
    tri[,c(3,1)]
  )
  
  edges <- t(apply(edges, 1, sort))
  edges <- unique(edges)
  
  # ---- Add edges as blue lines ----
  for (e in 1:nrow(edges)) {
    i1 <- edges[e,1]
    i2 <- edges[e,2]
    
    p <- p %>% add_trace(
      x = c(x[i1], x[i2]),
      y = c(y[i1], y[i2]),
      z = c(z[i1], z[i2]),
      type = "scatter3d",
      mode = "lines",
      line = list(color = "blue", width = 3),
      hoverinfo = "none",
      showlegend = FALSE
    )
  }
  
  return(p)
}

plot_3d_mesh_edges_faces <- function(mesh) {
  
  x <- mesh$loc[,1]
  y <- mesh$loc[,2]
  z <- mesh$loc[,3]
  tri <- mesh$graph$tv
  
  x_range <- range(x)
  y_range <- range(y)
  z_range <- range(z)
  
  # ---- Uniform gray color for faces ----
  gray_rgb <- rep(list(rgb(180, 180, 180, maxColorValue = 255)), length(x))
  white_rgb <- rep(list(rgb(255, 255, 255, alpha = 255, maxColorValue = 255)), length(x))
  
  df3 <- data.frame(x = x, 
                  y = y, 
                  z = z)
  
  # ---- Plot mesh faces ----
  p <- plot_ly() %>% add_trace(
    x = x,
    y = y,
    z = z,
    i = tri[,1] - 1,
    j = tri[,2] - 1,
    k = tri[,3] - 1,
    type = "mesh3d",
    vertexcolor = white_rgb,
    flatshading = TRUE,
    opacity = 1,
    showscale = FALSE
  )
  
  # ---- Extract unique edges ----
  edges <- rbind(
    tri[,1:2],
    tri[,2:3],
    tri[,c(3,1)]
  )
  edges <- t(apply(edges, 1, sort))
  edges <- unique(edges)
  
  # ---- Efficient edges as a single trace using NA breaks ----
  x_edges <- as.vector(t(cbind(x[edges[,1]], x[edges[,2]], NA)))
  y_edges <- as.vector(t(cbind(y[edges[,1]], y[edges[,2]], NA)))
  z_edges <- as.vector(t(cbind(z[edges[,1]], z[edges[,2]], NA)))
  
  p <- add_trace(
    p,
    x = x_edges, y = y_edges, z = z_edges,
    type = "scatter3d",
    mode = "lines",
    line = list(color = "black", width = 3),
    showlegend = FALSE,
    hoverinfo = "none"
  ) %>% 
    add_trace(data = df3, x = ~x, y = ~y, z = ~z, mode = "markers", type = "scatter3d", 
            marker = list(size = 4, color = "gray", symbol = 104)) %>%
    layout(
    scene = global.scene.setter.rec.and.sphere(x_range, y_range, z_range)
  )
  
  return(p)
}

plot_3d_mesh_edges_faces_with_hat <- function(mesh,
                                              hat_nodes = NULL,
                                              hat_height = 0.1,
                                              hat_color = NULL,
                                              hat_alpha = 0.6) {
  
  x <- mesh$loc[,1]
  y <- mesh$loc[,2]
  z <- mesh$loc[,3]
  n <- length(x)
  tri <- mesh$graph$tv
  
  x_range <- range(x)*2
  y_range <- range(y)*2
  z_range <- range(z)*2
  
  # ---- Extract unique edges once ----
  edges <- rbind(tri[,1:2], tri[,2:3], tri[,c(3,1)])
  edges <- t(apply(edges, 1, sort))
  edges <- unique(edges)
  
  # Base mesh colors
  white_rgb <- rep(list(rgb(255, 255, 255, alpha = 255, maxColorValue = 255)), n)
  
  df3 <- data.frame(x = x, y = y, z = z)
  
  # ---- Base spherical mesh ----
  p <- plot_ly() %>% add_trace(
    x = x, y = y, z = z,
    i = tri[,1] - 1, j = tri[,2] - 1, k = tri[,3] - 1,
    type = "mesh3d",
    vertexcolor = white_rgb,
    flatshading = TRUE,
    opacity = 1,
    showscale = FALSE,
    name = "Sphere mesh"
  )
  
  # ---- Base mesh edges (single efficient trace) ----
  x_edges <- as.vector(t(cbind(x[edges[,1]], x[edges[,2]], NA)))
  y_edges <- as.vector(t(cbind(y[edges[,1]], y[edges[,2]], NA)))
  z_edges <- as.vector(t(cbind(z[edges[,1]], z[edges[,2]], NA)))
  
 
  
  # ---- Hat functions on sphere ----
  if (!is.null(hat_nodes)) {
    
    # Validate indices
    if (any(hat_nodes < 1 | hat_nodes > n)) {
      stop("hat_nodes contains invalid node indices")
    }
    
    # Color recycling
    if (is.null(hat_color)) {
      hat_color <- grDevices::rainbow(length(hat_nodes))
    }
    hat_color <- rep(hat_color, length.out = length(hat_nodes))
    
    for (k in seq_along(hat_nodes)) {
      i0 <- hat_nodes[k]
      
      # Nodal hat basis
      phi <- rep(0, n)
      phi[i0] <- 1
      
      # Lift radially outward (important for sphere!)
      r <- sqrt(x^2 + y^2 + z^2)
      nx <- x / r
      ny <- y / r
      nz <- z / r
      
      x_hat <- x + hat_height * phi * nx
      y_hat <- y + hat_height * phi * ny
      z_hat <- z + hat_height * phi * nz
      
      # Hat surface color
      hat_rgb <- rep(list(adjustcolor(hat_color[k], alpha.f = hat_alpha)), n)
      
      # ---- Hat surface ----
      p <- add_trace(p,
        x = x_hat, y = y_hat, z = z_hat,
        i = tri[,1] - 1, j = tri[,2] - 1, k = tri[,3] - 1,
        type = "mesh3d",
        vertexcolor = hat_rgb,
        flatshading = TRUE,
        opacity = 1,
        name = paste0("phi_", i0),
        showlegend = FALSE
      )
      
      # ---- Hat wireframe edges ----
      x_hat_edges <- as.vector(t(cbind(x_hat[edges[,1]], x_hat[edges[,2]], NA)))
      y_hat_edges <- as.vector(t(cbind(y_hat[edges[,1]], y_hat[edges[,2]], NA)))
      z_hat_edges <- as.vector(t(cbind(z_hat[edges[,1]], z_hat[edges[,2]], NA)))
      
      p <- add_trace(p,
        x = x_hat_edges, y = y_hat_edges, z = z_hat_edges,
        type = "scatter3d", mode = "lines",
        line = list(color = hat_color[k], width = 2),
        showlegend = FALSE,
        hoverinfo = "none"
      )
    }
  }
  
   p <- add_trace(p,
    x = x_edges, y = y_edges, z = z_edges,
    type = "scatter3d", mode = "lines",
    line = list(color = "black", width = 3),
    showlegend = FALSE,
    hoverinfo = "none"
  )
   
  # ---- Nodes ----
  p <- add_trace(p,
    data = df3,
    x = ~x, y = ~y, z = ~z,
    mode = "markers",
    type = "scatter3d",
    marker = list(size = 4, color = "gray", symbol = 104),
    showlegend = FALSE
  )
  
  # ---- Scene ----
  p <- p %>% layout(
    scene = global.scene.setter.rec.and.sphere(x_range, y_range, z_range)
  )
  
  return(p)
}
graph.plotter.3d.onecol <- function(graph, vec) {
  
  # Coordinates on the mesh
  x <- plotting.order(graph$mesh$V[, 1], graph)
  y <- plotting.order(graph$mesh$V[, 2], graph)
  z <- plotting.order(vec, graph)  # vector to plot
  
  # Axis ranges
  x_range <- range(x)
  y_range <- range(y)
  z_range <- range(z)
  z_range[1] <- z_range[1] - 10^-4
  
  # Vertical lines (vectorized)
  n <- length(x)
  x_vert <- rep(x, each = 3)
  y_vert <- rep(y, each = 3)
  z_vert <- as.vector(t(cbind(0, z, NA)))
  
  # Create plot
  p <- plot_ly() %>%
    
    # Main 3D curve over the graph
    add_trace(
      x = x, y = y, z = z,
      type = "scatter3d", mode = "lines",
      line = list(color = "blue", width = 3),
      showlegend = FALSE
    ) %>%
    # Graph base
    add_trace(
      x = x, y = y, z = z*0,
      type = "scatter3d", mode = "lines",
      line = list(color = "black", width = 3),
      showlegend = FALSE
    ) %>%
    
    # Vertical lines from base to curve
    add_trace(
      x = x_vert, y = y_vert, z = z_vert,
      type = "scatter3d", mode = "lines",
      line = list(color = "gray", width = 0.5),
      showlegend = FALSE
    ) %>%
    
    layout(
      scene = list(xaxis = list(title = "x", range = x_range),
              yaxis = list(title = "y", range = y_range),
              zaxis = list(
      title = "z",
      range = z_range,
      exponentformat = "e",
      tickformat = NULL#".4f"     # prevents SI prefixes like μ, m, k
    ),
              aspectratio = list(x = 2*(1+2/pi), 
                                 y = 2*(2/pi), 
                                 z = 1*(2/pi)),
              camera = list(eye = list(x = 5, 
                                       y = 3, 
                                       z = 3.5),
                            center = list(x = (1+2/pi)/2, 
                                          y = 0, 
                                          z = 0)))
    )
  
  return(p)
}

8 References

grateful::cite_packages(output = "paragraph", out.dir = ".")

We used R version 4.5.2 (R Core Team 2025) and the following R packages: akima v. 0.6.3.6 (Akima and Gebhardt 2025), expm v. 1.0.0 (Maechler, Dutang, and Goulet 2024), fmesher v. 0.5.0 (Lindgren 2025), gsignal v. 0.3.7 (Van Boxtel, G.J.M., et al. 2021), here v. 1.0.1 (Müller 2020), htmltools v. 0.5.8.1 (Cheng et al. 2024), INLA v. 25.11.22 (Rue, Martino, and Chopin 2009; Lindgren, Rue, and Lindström 2011; Martins et al. 2013; Lindgren and Rue 2015; De Coninck et al. 2016; Rue et al. 2017; Verbosio et al. 2017; Bakka et al. 2018; Kourounis, Fuchs, and Schenk 2018), inlabru v. 2.13.0 (Yuan et al. 2017; Bachl et al. 2019), knitr v. 1.50 (Xie 2014, 2015, 2025a), Matrix v. 1.7.3 (Bates, Maechler, and Jagan 2025), MetricGraph v. 1.5.0.9000 (Bolin, Simas, and Wallin 2023a, 2023b, 2024, 2025; Bolin et al. 2024), neuralnet v. 1.44.2 (Fritsch, Guenther, and Wright 2019), orthopolynom v. 1.0.6.1 (Novomestky 2022), patchwork v. 1.3.1 (Pedersen 2025), pbmcapply v. 1.5.1 (Kuang, Kong, and Napolitano 2022), plotly v. 4.11.0 (Sievert 2020), posterdown v. 1.0 (Thorne 2019), pracma v. 2.4.4 (Borchers 2023), qrcode v. 0.3.0 (Onkelinx and Teh 2024), RColorBrewer v. 1.1.3 (Neuwirth 2022), RefManageR v. 1.4.0 (McLean 2014, 2017), renv v. 1.1.5 (Ushey and Wickham 2025), reshape2 v. 1.4.4 (Wickham 2007), reticulate v. 1.44.1 (Ushey, Allaire, and Tang 2025), rmarkdown v. 2.30 (Xie, Allaire, and Grolemund 2018; Xie, Dervieux, and Riederer 2020; Allaire et al. 2025), rSPDE v. 2.5.1.9000 (Bolin and Kirchner 2020; Bolin and Simas 2023; Bolin, Simas, and Xiong 2024), RSpectra v. 0.16.2 (Qiu and Mei 2024), scales v. 1.4.0 (Wickham, Pedersen, and Seidel 2025), slackr v. 3.4.0 (Kaye et al. 2025), tidyverse v. 2.0.0 (Wickham et al. 2019), viridisLite v. 0.4.2 (Garnier et al. 2023), xaringan v. 0.31 (Xie 2025b), xaringanExtra v. 0.8.0 (Aden-Buie and Warkentin 2024), xaringanthemer v. 0.4.4 (Aden-Buie 2025).

Aden-Buie, Garrick. 2025. xaringanthemer: Custom xaringan CSS Themes. https://doi.org/10.32614/CRAN.package.xaringanthemer.
Aden-Buie, Garrick, and Matthew T. Warkentin. 2024. xaringanExtra: Extras and Extensions for xaringan Slides. https://doi.org/10.32614/CRAN.package.xaringanExtra.
Akima, Hiroshi, and Albrecht Gebhardt. 2025. akima: Interpolation of Irregularly and Regularly Spaced Data. https://doi.org/10.32614/CRAN.package.akima.
Allaire, JJ, Yihui Xie, Christophe Dervieux, Jonathan McPherson, Javier Luraschi, Kevin Ushey, Aron Atkins, et al. 2025. rmarkdown: Dynamic Documents for r. https://github.com/rstudio/rmarkdown.
Bachl, Fabian E., Finn Lindgren, David L. Borchers, and Janine B. Illian. 2019. inlabru: An R Package for Bayesian Spatial Modelling from Ecological Survey Data.” Methods in Ecology and Evolution 10: 760–66. https://doi.org/10.1111/2041-210X.13168.
Bakka, Haakon, Håvard Rue, Geir-Arne Fuglstad, Andrea I. Riebler, David Bolin, Janine Illian, Elias Krainski, Daniel P. Simpson, and Finn K. Lindgren. 2018. “Spatial Modelling with INLA: A Review.” WIRES (Invited Extended Review) xx (Feb): xx–. http://arxiv.org/abs/1802.06350.
Bates, Douglas, Martin Maechler, and Mikael Jagan. 2025. Matrix: Sparse and Dense Matrix Classes and Methods. https://doi.org/10.32614/CRAN.package.Matrix.
Bolin, David, and Kristin Kirchner. 2020. “The Rational SPDE Approach for Gaussian Random Fields with General Smoothness.” Journal of Computational and Graphical Statistics 29 (2): 274–85. https://doi.org/10.1080/10618600.2019.1665537.
Bolin, David, Mihály Kovács, Vivek Kumar, and Alexandre B. Simas. 2024. “Regularity and Numerical Approximation of Fractional Elliptic Differential Equations on Compact Metric Graphs.” Mathematics of Computation 93 (349): 2439–72. https://doi.org/10.1090/mcom/3929.
Bolin, David, and Alexandre B. Simas. 2023. rSPDE: Rational Approximations of Fractional Stochastic Partial Differential Equations. https://CRAN.R-project.org/package=rSPDE.
Bolin, David, Alexandre B. Simas, and Jonas Wallin. 2023a. MetricGraph: Random Fields on Metric Graphs. https://CRAN.R-project.org/package=MetricGraph.
———. 2023b. “Statistical Inference for Gaussian Whittle-Matérn Fields on Metric Graphs.” arXiv Preprint arXiv:2304.10372. https://doi.org/10.48550/arXiv.2304.10372.
———. 2024. “Gaussian Whittle-Matérn Fields on Metric Graphs.” Bernoulli 30 (2): 1611–39. https://doi.org/10.3150/23-BEJ1647.
———. 2025. “Markov Properties of Gaussian Random Fields on Compact Metric Graphs.” Bernoulli. https://doi.org/10.48550/arXiv.2304.03190.
Bolin, David, Alexandre B. Simas, and Zhen Xiong. 2024. “Covariance-Based Rational Approximations of Fractional SPDEs for Computationally Efficient Bayesian Inference.” Journal of Computational and Graphical Statistics 33 (1): 64–74. https://doi.org/10.1080/10618600.2023.2231051.
Borchers, Hans W. 2023. pracma: Practical Numerical Math Functions. https://doi.org/10.32614/CRAN.package.pracma.
Cheng, Joe, Carson Sievert, Barret Schloerke, Winston Chang, Yihui Xie, and Jeff Allen. 2024. htmltools: Tools for HTML. https://github.com/rstudio/htmltools.
De Coninck, Arne, Bernard De Baets, Drosos Kourounis, Fabio Verbosio, Olaf Schenk, Steven Maenhout, and Jan Fostier. 2016. Needles: Toward Large-Scale Genomic Prediction with Marker-by-Environment Interaction.” Genetics 203 (1): 543–55. https://doi.org/10.1534/genetics.115.179887.
Fritsch, Stefan, Frauke Guenther, and Marvin N. Wright. 2019. neuralnet: Training of Neural Networks. https://doi.org/10.32614/CRAN.package.neuralnet.
Garnier, Simon, Ross, Noam, Rudis, Robert, Camargo, et al. 2023. viridis(Lite) - Colorblind-Friendly Color Maps for r. https://doi.org/10.5281/zenodo.4678327.
Kaye, Matt, Bob Rudis, Andrie de Vries, and Jonathan Sidi. 2025. slackr: Send Messages, Images, r Objects and Files to Slack Channels/Users. https://github.com/mrkaye97/slackr.
Kourounis, D., A. Fuchs, and O. Schenk. 2018. “Towards the Next Generation of Multiperiod Optimal Power Flow Solvers.” IEEE Transactions on Power Systems PP (99): 1–10. https://doi.org/10.1109/TPWRS.2017.2789187.
Kuang, Kevin, Quyu Kong, and Francesco Napolitano. 2022. pbmcapply: Tracking the Progress of Mc*pply with Progress Bar. https://doi.org/10.32614/CRAN.package.pbmcapply.
Lindgren, Finn. 2025. fmesher: Triangle Meshes and Related Geometry Tools. https://github.com/inlabru-org/fmesher.
Lindgren, Finn, and Håvard Rue. 2015. “Bayesian Spatial Modelling with R-INLA.” Journal of Statistical Software 63 (19): 1–25. http://www.jstatsoft.org/v63/i19/.
Lindgren, Finn, Håvard Rue, and Johan Lindström. 2011. “An Explicit Link Between Gaussian Fields and Gaussian Markov Random Fields: The Stochastic Partial Differential Equation Approach (with Discussion).” Journal of the Royal Statistical Society B 73 (4): 423–98.
Maechler, Martin, Christophe Dutang, and Vincent Goulet. 2024. expm: Matrix Exponential, Log, etc. https://doi.org/10.32614/CRAN.package.expm.
Martins, Thiago G., Daniel Simpson, Finn Lindgren, and Håvard Rue. 2013. “Bayesian Computing with INLA: New Features.” Computational Statistics and Data Analysis 67: 68–83.
McLean, Mathew William. 2014. Straightforward Bibliography Management in r Using the RefManager Package. https://arxiv.org/abs/1403.2036.
———. 2017. RefManageR: Import and Manage BibTeX and BibLaTeX References in r.” The Journal of Open Source Software. https://doi.org/10.21105/joss.00338.
Müller, Kirill. 2020. here: A Simpler Way to Find Your Files. https://doi.org/10.32614/CRAN.package.here.
Neuwirth, Erich. 2022. RColorBrewer: ColorBrewer Palettes.
Novomestky, Frederick. 2022. orthopolynom: Collection of Functions for Orthogonal and Orthonormal Polynomials. https://doi.org/10.32614/CRAN.package.orthopolynom.
Onkelinx, Thierry, and Victor Teh. 2024. qrcode: Generate QRcodes with r. Version 0.3.0. https://doi.org/10.5281/zenodo.5040088.
Pedersen, Thomas Lin. 2025. patchwork: The Composer of Plots. https://doi.org/10.32614/CRAN.package.patchwork.
Qiu, Yixuan, and Jiali Mei. 2024. RSpectra: Solvers for Large-Scale Eigenvalue and SVD Problems. https://doi.org/10.32614/CRAN.package.RSpectra.
R Core Team. 2025. R: A Language and Environment for Statistical Computing. Vienna, Austria: R Foundation for Statistical Computing. https://www.R-project.org/.
Rue, Håvard, Sara Martino, and Nicholas Chopin. 2009. “Approximate Bayesian Inference for Latent Gaussian Models Using Integrated Nested Laplace Approximations (with Discussion).” Journal of the Royal Statistical Society B 71: 319–92.
Rue, Håvard, Andrea I. Riebler, Sigrunn H. Sørbye, Janine B. Illian, Daniel P. Simpson, and Finn K. Lindgren. 2017. “Bayesian Computing with INLA: A Review.” Annual Reviews of Statistics and Its Applications 4 (March): 395–421. http://arxiv.org/abs/1604.00860.
Sievert, Carson. 2020. Interactive Web-Based Data Visualization with r, Plotly, and Shiny. Chapman; Hall/CRC. https://plotly-r.com.
Thorne, W. Brent. 2019. posterdown: An r Package Built to Generate Reproducible Conference Posters for the Academic and Professional World Where Powerpoint and Pages Just Won’t Cut It. https://github.com/brentthorne/posterdown.
Ushey, Kevin, JJ Allaire, and Yuan Tang. 2025. reticulate: Interface to Python. https://doi.org/10.32614/CRAN.package.reticulate.
Ushey, Kevin, and Hadley Wickham. 2025. renv: Project Environments. https://rstudio.github.io/renv/.
Van Boxtel, G.J.M., et al. 2021. gsignal: Signal Processing. https://github.com/gjmvanboxtel/gsignal.
Verbosio, Fabio, Arne De Coninck, Drosos Kourounis, and Olaf Schenk. 2017. “Enhancing the Scalability of Selected Inversion Factorization Algorithms in Genomic Prediction.” Journal of Computational Science 22 (Supplement C): 99–108. https://doi.org/10.1016/j.jocs.2017.08.013.
Wickham, Hadley. 2007. “Reshaping Data with the reshape Package.” Journal of Statistical Software 21 (12): 1–20. http://www.jstatsoft.org/v21/i12/.
Wickham, Hadley, Mara Averick, Jennifer Bryan, Winston Chang, Lucy D’Agostino McGowan, Romain François, Garrett Grolemund, et al. 2019. “Welcome to the tidyverse.” Journal of Open Source Software 4 (43): 1686. https://doi.org/10.21105/joss.01686.
Wickham, Hadley, Thomas Lin Pedersen, and Dana Seidel. 2025. scales: Scale Functions for Visualization. https://scales.r-lib.org.
Xie, Yihui. 2014. knitr: A Comprehensive Tool for Reproducible Research in R.” In Implementing Reproducible Computational Research, edited by Victoria Stodden, Friedrich Leisch, and Roger D. Peng. Chapman; Hall/CRC.
———. 2015. Dynamic Documents with R and Knitr. 2nd ed. Boca Raton, Florida: Chapman; Hall/CRC. https://yihui.org/knitr/.
———. 2025a. knitr: A General-Purpose Package for Dynamic Report Generation in R. https://yihui.org/knitr/.
———. 2025b. xaringan: Presentation Ninja. https://doi.org/10.32614/CRAN.package.xaringan.
Xie, Yihui, J. J. Allaire, and Garrett Grolemund. 2018. R Markdown: The Definitive Guide. Boca Raton, Florida: Chapman; Hall/CRC. https://bookdown.org/yihui/rmarkdown.
Xie, Yihui, Christophe Dervieux, and Emily Riederer. 2020. R Markdown Cookbook. Boca Raton, Florida: Chapman; Hall/CRC. https://bookdown.org/yihui/rmarkdown-cookbook.
Yuan, Yuan, Bachl, Fabian E., Lindgren, Finn, Borchers, et al. 2017. “Point Process Models for Spatio-Temporal Distance Sampling Data from a Large-Scale Survey of Blue Whales.” Ann. Appl. Stat. 11 (4): 2270–97. https://doi.org/10.1214/17-AOAS1078.
LS0tCnRpdGxlOiAiRnVuY3Rpb25hbGl0eSIKZGF0ZTogIkxhc3QgbW9kaWZpZWQ6IGByIGZvcm1hdChTeXMudGltZSgpLCAnJWQtJW0tJVkuJylgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIG1hdGhqYXg6ICJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL21hdGhqYXhAMy9lczUvdGV4LW1tbC1jaHRtbC5qcyIKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRoZW1lOiBmbGF0bHkKICAgIGNvZGVfZm9sZGluZzogc2hvdyAjIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUiIHRvIGhpZGUgY29kZSBhbmQgYWRkIGEgYnV0dG9uIHRvIHNob3cgaXQKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGZpZ19jYXB0aW9uOiB0cnVlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjc3M6IHZpc3VhbC5jc3MKYWx3YXlzX2FsbG93X2h0bWw6IHRydWUKYmlibGlvZ3JhcGh5OiAKICAtIHJlZmVyZW5jZXMuYmliCiAgLSBncmF0ZWZ1bC1yZWZzLmJpYgpoZWFkZXItaW5jbHVkZXM6CiAgLSBcbmV3Y29tbWFuZHtcYXJ9e1xtYXRoYmJ7Un19CiAgLSBcbmV3Y29tbWFuZHtcbGxhdn1bMV17XGxlZnRceyMxXHJpZ2h0XH19CiAgLSBcbmV3Y29tbWFuZHtccGFyZX1bMV17XGxlZnQoIzFccmlnaHQpfQogIC0gXG5ld2NvbW1hbmR7XE5jYWx9e1xtYXRoY2Fse059fQogIC0gXG5ld2NvbW1hbmR7XFZjYWx9e1xtYXRoY2Fse1Z9fQogIC0gXG5ld2NvbW1hbmR7XEVjYWx9e1xtYXRoY2Fse0V9fQogIC0gXG5ld2NvbW1hbmR7XFdjYWx9e1xtYXRoY2Fse1d9fQotLS0KCkdvIGJhY2sgdG8gdGhlIFtDb250ZW50c10oYWJvdXQuaHRtbCkgcGFnZS4KCjxkaXYgc3R5bGU9ImNvbG9yOiAjMmMzZTUwOyB0ZXh0LWFsaWduOiByaWdodDsiPgoqKioqKioqKiAgCjxzdHJvbmc+UHJlc3MgU2hvdyB0byByZXZlYWwgdGhlIGNvZGUgY2h1bmtzLjwvc3Ryb25nPiAgCgoqKioqKioqKgo8L2Rpdj4KCgpgYGB7ciwgcHVybCA9IEZBTFNFLCBlY2hvID0gRkFMU0V9CiMgQ3JlYXRlIGEgY2xpcGJvYXJkIGJ1dHRvbiBvbiB0aGUgcmVuZGVyZWQgSFRNTCBwYWdlCnNvdXJjZShoZXJlOjpoZXJlKCJjbGlwYm9hcmQuUiIpKTsgY2xpcGJvYXJkCmBgYAoKCmBgYHtyLCBwdXJsID0gRkFMU0UsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQojIFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoMTk4MikgCiMgU2V0IGdsb2JhbCBvcHRpb25zIGZvciBhbGwgY29kZSBjaHVua3MKa25pdHI6Om9wdHNfY2h1bmskc2V0KAogICMgRGlzYWJsZSBtZXNzYWdlcyBwcmludGVkIGJ5IFIgY29kZSBjaHVua3MKICBtZXNzYWdlID0gRkFMU0UsICAgIAogICMgRGlzYWJsZSB3YXJuaW5ncyBwcmludGVkIGJ5IFIgY29kZSBjaHVua3MKICB3YXJuaW5nID0gRkFMU0UsICAgIAogICMgU2hvdyBSIGNvZGUgd2l0aGluIGNvZGUgY2h1bmtzIGluIG91dHB1dAogIGVjaG8gPSBUUlVFLCAgICAgICAgCiAgIyBJbmNsdWRlIGJvdGggUiBjb2RlIGFuZCBpdHMgcmVzdWx0cyBpbiBvdXRwdXQKICBpbmNsdWRlID0gVFJVRSwgICAgIAogICMgRXZhbHVhdGUgUiBjb2RlIGNodW5rcwogIGV2YWwgPSBUUlVFLCAgICAgICAKICAjIEVuYWJsZSBjYWNoaW5nIG9mIFIgY29kZSBjaHVua3MgZm9yIGZhc3RlciByZW5kZXJpbmcKICBjYWNoZSA9IEZBTFNFLCAgICAgIAogICMgQWxpZ24gZmlndXJlcyBpbiB0aGUgY2VudGVyIG9mIHRoZSBvdXRwdXQKICBmaWcuYWxpZ24gPSAiY2VudGVyIiwKICAjIEVuYWJsZSByZXRpbmEgZGlzcGxheSBmb3IgaGlnaC1yZXNvbHV0aW9uIGZpZ3VyZXMKICByZXRpbmEgPSAyLAogICMgU2hvdyBlcnJvcnMgaW4gdGhlIG91dHB1dCBpbnN0ZWFkIG9mIHN0b3BwaW5nIHJlbmRlcmluZwogIGVycm9yID0gVFJVRSwKICAjIERvIG5vdCBjb2xsYXBzZSBjb2RlIGFuZCBvdXRwdXQgaW50byBhIHNpbmdsZSBibG9jawogIGNvbGxhcHNlID0gRkFMU0UKKQojIFN0YXJ0IHRoZSBmaWd1cmUgY291bnRlcgpmaWdfY291bnQgPC0gMAojIERlZmluZSB0aGUgY2FwdGlvbmVyIGZ1bmN0aW9uCmNhcHRpb25lciA8LSBmdW5jdGlvbihjYXB0aW9uKSB7CiAgZmlnX2NvdW50IDw8LSBmaWdfY291bnQgKyAxCiAgcGFzdGUwKCJGaWd1cmUgIiwgZmlnX2NvdW50LCAiOiAiLCBjYXB0aW9uKQp9CmBgYAoKCgpgYGB7cn0KIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiZGF2aWRib2xpbi9yc3BkZSIsIHJlZiA9ICJkZXZlbCIpCiMgcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoImRhdmlkYm9saW4vbWV0cmljZ3JhcGgiLCByZWYgPSAiZGV2ZWwiKQpsaWJyYXJ5KHJTUERFKQpsaWJyYXJ5KE1ldHJpY0dyYXBoKQpsaWJyYXJ5KGdyYXRlZnVsKQoKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KHBsb3RseSkKYGBgCgoKIyMgRnJhY3Rpb25hbCBkaWZmdXNpb24gZXF1YXRpb25zIG9uIG1ldHJpYyBncmFwaHMKCldlIGFuYWx5emUgYW5kIG51bWVyaWNhbGx5IGFwcHJveGltYXRlIHNvbHV0aW9ucyB0byBmcmFjdGlvbmFsIGRpZmZ1c2lvbiBlcXVhdGlvbnMgb24gbWV0cmljIGdyYXBocyBvZiB0aGUgZm9ybQpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTptYWluZXF9Clx0YWd7MX0KXGxlZnRcewpcYmVnaW57YWxpZ25lZH0KICAgIFxwYXJ0aWFsX3QgdShzLHQpICsgKFxrYXBwYV4yIC0gXERlbHRhX1xHYW1tYSlee1xhbHBoYS8yfSB1KHMsdCkgJj0gZihzLHQpLCAmJiBccXVhZCAocyx0KSBcaW4gXEdhbW1hIFx0aW1lcyAoMCwgVCksIFxcCiAgICB1KHMsMCkgJj0gdV8wKHMpLCAmJiBccXVhZCBzIFxpbiBcR2FtbWEsClxlbmR7YWxpZ25lZH0KXHJpZ2h0LgpcZW5ke2VxdWF0aW9ufQp3aXRoICR1KFxjZG90LHQpJCBzYXRpc2Z5aW5nIHRoZSBLaXJjaGhvZmYgdmVydGV4IGNvbmRpdGlvbnMKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXE6S2NvbmR9Clx0YWd7Mn0KICAgXG1hdGhjYWx7S30gPSAgXGxlZnRce1xwaGlcaW4gQyhcR2FtbWEpXDtcbWlkZGxlfFw7IFxmb3JhbGwgdlxpbiBcbWF0aGNhbHtWfTpcOyBcc3VtX3tlXGluXG1hdGhjYWx7RX1fdn1ccGFydGlhbF9lIFxwaGkodik9MCBccmlnaHRcfS4KXGVuZHtlcXVhdGlvbn0KSGVyZSAkXEdhbW1hID0gKFxtYXRoY2Fse1Z9LFxtYXRoY2Fse0V9KSQgaXMgYSBtZXRyaWMgZ3JhcGgsICRca2FwcGE+MCQsICRcYWxwaGFcaW4oMCwyXSQgZGV0ZXJtaW5lcyB0aGUgc21vb3RobmVzcyBvZiAkdShcY2RvdCx0KSQsICRcRGVsdGFfe1xHYW1tYX0kIGlzIHRoZSBzby1jYWxsZWQgS2lyY2hob2ZmLS1MYXBsYWNpYW4sIGFuZCAkZjpcR2FtbWFcdGltZXMgKDAsVClcdG9cbWF0aGJie1J9JCBhbmQgJHVfMDogXEdhbW1hIFx0byBcbWF0aGJie1J9JCBhcmUgZml4ZWQgZnVuY3Rpb25zLCBjYWxsZWQgcmlnaHQtaGFuZCBzaWRlIGFuZCBpbml0aWFsIGNvbmRpdGlvbiwgcmVzcGVjdGl2ZWx5LgoKIyMgTnVtZXJpY2FsIFNjaGVtZSB7I251bV9zY2hlbWV9CgpMZXQgJFxhbHBoYVxpbigwLDJdJCBhbmQgJFVfaF5cdGF1JCBkZW5vdGUgdGhlIHNlcXVlbmNlIG9mIGFwcHJveGltYXRpb25zIG9mIHRoZSBzb2x1dGlvbiB0byB0aGUgd2VhayBmb3JtIG9mIHByb2JsZW0gXGVxcmVme2VxOm1haW5lcX0gYXQgZWFjaCB0aW1lIHN0ZXAgb24gYSBtZXNoIGluZGV4ZWQgYnkgJGgkLiBUaGUgc2NoZW1lIGNvbXB1dGVzICRVX2hee1x0YXV9XHN1YnNldCBWX2gkLCB3aGljaCBzb2x2ZXMKXGJlZ2lue2VxdWF0aW9ufQogICAgXGxlZnRcewpcYmVnaW57YWxpZ25lZH0KICAgIFxsYW5nbGVcZGVsdGEgVV9oXntrKzF9LFxwaGlccmFuZ2xlICsgXG1hdGhmcmFre2F9KAogICAgICAgIFVfaF57aysxfSxccGhpKSAmPSBcbGFuZ2xlIGZee2srMX0sXHBoaVxyYW5nbGUsIFxxdWFkXGZvcmFsbFxwaGlcaW4gVl9oLFxxdWFkIGs9MCxcZG90cywgTi0xLCBcXAogICAgVV4wX2ggJj0gUF9odV8wLCAKXGVuZHthbGlnbmVkfQpccmlnaHQuClxlbmR7ZXF1YXRpb259CndoZXJlICRmXntrKzF9ID0gXGRpc3BsYXlzdHlsZVxkZnJhY3sxfXtcdGF1fVxpbnRfe3Rfa31ee3Rfe2srMX19Zih0KWR0JC4gVGhlIGFib3ZlIGV4cHJlc3Npb24gY2FuIGJlIGVxdWl2YWxlbnRseSB3cml0dGVuIGFzClxiZWdpbntlcXVhdGlvbn0KICAgIFxsZWZ0XHsKXGJlZ2lue2FsaWduZWR9CiAgICBcbGFuZ2xlXGRmcmFje1VfaF57aysxfSAtIFVfaF57a319e1x0YXV9LFxwaGlccmFuZ2xlICsgXG1hdGhmcmFre2F9KAogICAgICAgIFVfaF57aysxfSxccGhpKSAmPSBcbGFuZ2xlIGZee2srMX0sXHBoaVxyYW5nbGUsIFxxdWFkXGZvcmFsbFxwaGlcaW4gVl9oLFxxdWFkIGs9MCxcZG90cywgTi0xLCBcXAogICAgVV4wX2ggJj0gUF9odV8wLCAKXGVuZHthbGlnbmVkfQpccmlnaHQuClxlbmR7ZXF1YXRpb259Cm9yClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse3N5c3RlbTpmdWxseV9kaXNjcmV0ZV9zY2hlbWV9Clx0YWd7M30KICAgIFxsZWZ0XHsKXGJlZ2lue2FsaWduZWR9CiAgICBcbGFuZ2xlIFVfaF57aysxfSxccGhpXHJhbmdsZSArIFx0YXVcbWF0aGZyYWt7YX0oCiAgICAgICAgVV9oXntrKzF9LFxwaGkpICY9IFxsYW5nbGUgVV9oXntrfSxccGhpXHJhbmdsZSArIFx0YXVcbGFuZ2xlIGZee2srMX0sXHBoaVxyYW5nbGUsIFxxdWFkXGZvcmFsbFxwaGlcaW4gVl9oLFxxdWFkIGs9MCxcZG90cywgTi0xLCBcXAogICAgVV4wX2ggJj0gUF9odV8wLgpcZW5ke2FsaWduZWR9ClxyaWdodC4KXGVuZHtlcXVhdGlvbn0KCkF0IGVhY2ggdGltZSBzdGVwICR0X2skLCB0aGUgZmluaXRlIGVsZW1lbnQgc29sdXRpb24gJFVfaF5rXGluIFZfaCQgdG8gXGVxcmVme3N5c3RlbTpmdWxseV9kaXNjcmV0ZV9zY2hlbWV9IGNhbiBiZSBleHByZXNzZWQgYXMgYSBsaW5lYXIgY29tYmluYXRpb24gb2YgdGhlIGJhc2lzIGZ1bmN0aW9ucyAgJFx7XHBzaV5pX2hcfV97aT0xfV57Tl9ofSQgaW50cm9kdWNlZCBpbiB0aGUgW1ByZWxpbWluYXJpZXNdKHByZWxpbWluYXJpZXMuaHRtbCNmZW0tYmFzaXMpIHBhZ2UsIG5hbWVseSwgClxiZWdpbnthbGlnbn0KXGxhYmVse251bV9zb2x9Clx0YWd7NH0KICAgIFVfaF5rKHMpID0gIFxzdW1fe2k9MX1ee05faH11X2lea1xwc2leaV9oKHMpLCBcO3NcaW5cR2FtbWEuClxlbmR7YWxpZ259CgpSZXBsYWNpbmcgXGVxcmVme251bV9zb2x9IGludG8gXGVxcmVme3N5c3RlbTpmdWxseV9kaXNjcmV0ZV9zY2hlbWV9IHlpZWxkcyB0aGUgZm9sbG93aW5nIHN5c3RlbQpcYmVnaW57YWxpZ24qfQogICAgXHN1bV97aj0xfV57Tl9ofXVfal57aysxfVsoXHBzaV9oXmosXHBzaV9oXmkpX3tMXzIoXEdhbW1hKX0rIFx0YXVcbWF0aGZyYWt7YX0oXHBzaV9oXmosXHBzaV9oXmkpXSA9IFxzdW1fe2o9MX1ee05faH11X2pee2t9KFxwc2lfaF5qLFxwc2lfaF5pKV97TF8yKFxHYW1tYSl9K1x0YXUoIGZee2srMX0sXHBzaV9oXmkpX3tMXzIoXEdhbW1hKX0sXHF1YWQgaSA9IDEsXGRvdHMsIE5faC4KXGVuZHthbGlnbip9CkluIG1hdHJpeCBub3RhdGlvbiwKXGJlZ2lue2FsaWdufQpcbGFiZWx7ZGlmZl9lcV9kaXNjcmV0ZX0KICAgIChcbWF0aGJme0N9K1x0YXUgXG1hdGhiZntMfV57XGFscGhhLzJ9KVxtYXRoYmZ7VX1ee2srMX0gPSBcbWF0aGJme0N9XG1hdGhiZntVfV5rK1x0YXUgXG1hdGhiZntGfV57aysxfSwKXGVuZHthbGlnbn0Kb3IgYnkgaW50cm9kdWNpbmcgdGhlIHNjYWxpbmcgcGFyYW1ldGVyICRca2FwcGFeMj4wJCwKXGJlZ2lue2FsaWdufQogICAgKFxtYXRoYmZ7Q30rXHRhdSAoXGthcHBhXjIpXntcYWxwaGEvMn0oXG1hdGhiZntMfS9ca2FwcGFeMilee1xhbHBoYS8yfSlcbWF0aGJme1V9XntrKzF9ID0gXG1hdGhiZntDfVxtYXRoYmZ7VX1eaytcdGF1IFxtYXRoYmZ7Rn1ee2srMX0sClxlbmR7YWxpZ259CndoZXJlICRcbWF0aGJme0N9JCBoYXMgZW50cmllcyAkXG1hdGhiZntDfV97aSxqfSA9IChccHNpX2heaSxccHNpX2heailfe0xfMihcR2FtbWEpfSQsICRcbWF0aGJme0x9XntcYWxwaGEvMn0kIGhhcyBlbnRyaWVzICRcbWF0aGZyYWt7YX0oXHBzaV9oXmksXHBzaV9oXmopJCwgJFxtYXRoYmZ7VX1eayQgaGFzIGNvbXBvbmVudHMgJHVfal5rJCwgYW5kICRcbWF0aGJme0Z9XmskIGhhcyBjb21wb25lbnRzICQoIGZee2t9LFxwc2lfaF5pKV97TF8yKFxHYW1tYSl9JC4gQXBwbHlpbmcgJChcbWF0aGJme0x9L1xrYXBwYV4yKV57LVxhbHBoYS8yfSQgdG8gYm90aCBzaWRlcyB5aWVsZHMKXGJlZ2lue2VxdWF0aW9ufQooKFxtYXRoYmZ7TH0vXGthcHBhXjIpXnstXGFscGhhLzJ9XG1hdGhiZntDfStcdGF1IChca2FwcGFeMilee1xhbHBoYS8yfVxtYXRoYmZ7SX0pXG1hdGhiZntVfV57aysxfSA9IChcbWF0aGJme0x9L1xrYXBwYV4yKV57LVxhbHBoYS8yfShcbWF0aGJme0N9XG1hdGhiZntVfV5rK1x0YXUgXG1hdGhiZntGfV57aysxfSkuClxlbmR7ZXF1YXRpb259CkZvbGxvd2luZyBAclNQREUyMDIwLCB3ZSBhcHByb3hpbWF0ZSAkKFxtYXRoYmZ7TH0vXGthcHBhXjIpXnstXGFscGhhLzJ9JCBieSAkXG1hdGhiZntQfV9cZWxsXnstXHRvcH1cbWF0aGJme1B9X3JeXHRvcCQgdG8gYXJyaXZlIGF0ClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse2VxOnNjaGVtZTJ9Clx0YWd7NX0KKFxtYXRoYmZ7UH1fXGVsbF57LVx0b3B9XG1hdGhiZntQfV9yXlx0b3AgXG1hdGhiZntDfStcdGF1KFxrYXBwYV4yKV57XGFscGhhLzJ9IFxtYXRoYmZ7SX0pXG1hdGhiZntVfV57aysxfSA9IFxtYXRoYmZ7UH1fXGVsbF57LVx0b3B9XG1hdGhiZntQfV9yXlx0b3AoXG1hdGhiZntDfVxtYXRoYmZ7VX1eaytcdGF1IFxtYXRoYmZ7Rn1ee2srMX0pLgpcZW5ke2VxdWF0aW9ufQp3aGVyZQpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTpQTFBSfQpcdGFnezZ9ClxtYXRoYmZ7UH1fciA9IFxwcm9kX3tpPTF9Xm0gXGxlZnQoXG1hdGhiZntJfS1yX3sxaX1cZGZyYWN7XG1hdGhiZntDfV57LTF9XG1hdGhiZntMfX17XGthcHBhXjJ9XHJpZ2h0KVxxdWFkXHRleHR7YW5kfVxxdWFkIFxtYXRoYmZ7UH1fXGVsbCA9IFxkZnJhY3sxfXtcdGV4dHR0e2ZhY3Rvcn19XG1hdGhiZntDfVxwcm9kX3tqPTF9XnttKzF9IFxsZWZ0KFxtYXRoYmZ7SX0tcl97Mmp9XGRmcmFje1xtYXRoYmZ7Q31eey0xfVxtYXRoYmZ7TH19e1xrYXBwYV4yfVxyaWdodCksClxlbmR7ZXF1YXRpb259CmFuZCAkXHRleHR0dHtmYWN0b3J9ID0gXGRmcmFje2NfbX17Yl97bSsxfX0kLCBhbmQKJFx7cl97MWl9XH1fe2k9MX1ebSQgYW5kICRce3JfezJqfVx9X3tqPTF9XnttKzF9JCBhcmUgdGhlIHJvb3RzIG9mICRxXzEoeCkgPVxzdW1fe2k9MH1ebWNfaXhee2l9JCBhbmQgICRxXzIoeCk9XHN1bV97aj0wfV57bSsxfWJfanhee2p9JCwgcmVzcGVjdGl2ZWx5LiBUaGUgY29lZmZpY2llbnRzICAkXHtjX2lcfV97aT0wfV5tJCBhbmQgICRce2Jfalx9X3tqPTB9XnttKzF9JCBhcmUgZGV0ZXJtaW5lZCBhcyB0aGUgYmVzdCByYXRpb25hbCBhcHByb3hpbWF0aW9uICRxXzEvcV8yJCBvZiB0aGUgZnVuY3Rpb24gJHhee1xhbHBoYS8yLTF9JCBvdmVyIHRoZSBpbnRlcnZhbCAkSl9oOiA9IFtca2FwcGFeezJ9XGxhbWJkYV97Tl9oLGh9XnstMX0sIFxrYXBwYV57Mn1cbGFtYmRhX3sxLGh9XnstMX1dJCwgd2hlcmUgJFxsYW1iZGFfezEsaH0sIFxsYW1iZGFfe05faCxofT4wJCBhcmUgdGhlIHNtYWxsZXN0IGFuZCB0aGUgbGFyZ2VzdCBlaWdlbnZhbHVlIG9mICRMX2gkLCByZXNwZWN0aXZlbHkuCgoKRm9yIHRoZSBzYWtlIG9mIGNsYXJpdHksIHdlIG5vdGUgdGhhdCB0aGUgbnVtZXJpY2FsIGltcGxlbWVudGF0aW9uIG9mIEByU1BERTIwMjAgYWN0dWFsbHkgZGVmaW5lcyAkXG1hdGhiZntQfV9yJCBhbmQgJFxtYXRoYmZ7UH1fXGVsbCQgYXMKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXE6UExQUmJvbGlufQpcdGFnezd9ClxtYXRoYmZ7UH1fciA9IFxwcm9kX3tpPTF9Xm0gXGxlZnQoXG1hdGhiZntJfS1yX3sxaX1cZGZyYWN7XG1hdGhiZntDfV57LTF9XG1hdGhiZntMfX17XGthcHBhXjJ9XHJpZ2h0KVxxdWFkXHRleHR7YW5kfVxxdWFkIFxtYXRoYmZ7UH1fXGVsbCA9IFxkZnJhY3tca2FwcGFeezJcYmV0YX19e1x0ZXh0dHR7ZmFjdG9yfX1cbWF0aGJme0N9XHByb2Rfe2o9MX1ee20rMX0gXGxlZnQoXG1hdGhiZntJfS1yX3syan1cZGZyYWN7XG1hdGhiZntDfV57LTF9XG1hdGhiZntMfX17XGthcHBhXjJ9XHJpZ2h0KSwKXGVuZHtlcXVhdGlvbn0Kd2hlcmUgJFxiZXRhID0gXGFscGhhLzIkIGFuZCB0aGUgc2NhbGluZyBmYWN0b3IgJChca2FwcGFeMilee1xhbHBoYS8yfSQgb3IgJFxrYXBwYV57MlxiZXRhfSQgaXMgYWxyZWFkeSBpbmNvcnBvcmF0ZWQgaW4gJFxtYXRoYmZ7UH1fXGVsbCQsIGEgY29udmVudGlvbiB3ZSBhZG9wdCBpbiB0aGUgZm9sbG93aW5nLiBXaXRoIHRoaXMgdW5kZXIgY29uc2lkZXJhdGlvbiwgd2UgY2FuIHJld3JpdGUgXGVxcmVme2VxOnNjaGVtZTJ9IGFzClxiZWdpbntlcXVhdGlvbn0KXHRhZ3s4fQooXG1hdGhiZntQfV9yXlx0b3AgXG1hdGhiZntDfStcdGF1IFxtYXRoYmZ7UH1fXGVsbF5cdG9wKVxtYXRoYmZ7VX1ee2srMX0gPSBcbWF0aGJme1B9X3JeXHRvcChcbWF0aGJme0N9XG1hdGhiZntVfV5rK1x0YXUgXG1hdGhiZntGfV57aysxfSksClxsYWJlbHtlcTpzY2hlbWV9ClxlbmR7ZXF1YXRpb259CndoZXJlICAKXGJlZ2lue2VxdWF0aW9ufQpcbWF0aGJme1B9X3JeXHRvcCA9IFxwcm9kX3tpPTF9Xm0gXGxlZnQoXG1hdGhiZntJfS1yX3sxaX1cZGZyYWN7XG1hdGhiZntMfVxtYXRoYmZ7Q31eey0xfX17XGthcHBhXjJ9XHJpZ2h0KVxxdWFkXHRleHR7YW5kfVxxdWFkIFxtYXRoYmZ7UH1fXGVsbF5cdG9wID0gXGRmcmFje1xrYXBwYV57MlxiZXRhfX17XHRleHR0dHtmYWN0b3J9fVxwcm9kX3tqPTF9XnttKzF9IFxsZWZ0KFxtYXRoYmZ7SX0tcl97Mmp9XGRmcmFje1xtYXRoYmZ7TH1cbWF0aGJme0N9XnstMX19e1xrYXBwYV4yfVxyaWdodClcY2RvdCBcbWF0aGJme0N9ClxlbmR7ZXF1YXRpb259CnNpbmNlICRcbWF0aGJme0x9JCBhbmQgJFxtYXRoYmZ7Q31eey0xfSQgYXJlIHN5bW1ldHJpYyBhbmQgdGhlIGZhY3RvcnMgaW4gdGhlIHByb2R1Y3QgY29tbXV0ZS4gUmVwbGFjaW5nIHRoZXNlIHR3byBpbnRvIFxlcXJlZntlcTpzY2hlbWV9IHlpZWxkcwpcYmVnaW57ZXF1YXRpb259ClxsZWZ0KFxwcm9kX3tpPTF9Xm0gXGxlZnQoXG1hdGhiZntJfS1yX3sxaX1cZGZyYWN7XG1hdGhiZntMfVxtYXRoYmZ7Q31eey0xfX17XGthcHBhXjJ9XHJpZ2h0KStcZGZyYWN7XHRhdSBca2FwcGFeezJcYmV0YX19e1x0ZXh0dHR7ZmFjdG9yfX1ccHJvZF97aj0xfV57bSsxfSBcbGVmdChcbWF0aGJme0l9LXJfezJqfVxkZnJhY3tcbWF0aGJme0x9XG1hdGhiZntDfV57LTF9fXtca2FwcGFeMn1ccmlnaHQpXHJpZ2h0KVxtYXRoYmZ7Q31cbWF0aGJme1V9XntrKzF9ID0gXHByb2Rfe2k9MX1ebSBcbGVmdChcbWF0aGJme0l9LXJfezFpfVxkZnJhY3tcbWF0aGJme0x9XG1hdGhiZntDfV57LTF9fXtca2FwcGFeMn1ccmlnaHQpXGNkb3QgKFxtYXRoYmZ7Q31cbWF0aGJme1V9XmsrXHRhdSBcbWF0aGJme0Z9XntrKzF9KSwKXGVuZHtlcXVhdGlvbn0KdGhhdCBpcywKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXE6ZmluYWxfc2NoZW1lfQpcdGFnezl9ClxtYXRoYmZ7VX1ee2srMX0gPSBcbWF0aGJme0N9XnstMX1cbGVmdChccHJvZF97aT0xfV5tIFxsZWZ0KFxtYXRoYmZ7SX0tcl97MWl9XGRmcmFje1xtYXRoYmZ7TH1cbWF0aGJme0N9XnstMX19e1xrYXBwYV4yfVxyaWdodCkrXGRmcmFje1x0YXUgXGthcHBhXnsyXGJldGF9fXtcdGV4dHR0e2ZhY3Rvcn19XHByb2Rfe2o9MX1ee20rMX0gXGxlZnQoXG1hdGhiZntJfS1yX3syan1cZGZyYWN7XG1hdGhiZntMfVxtYXRoYmZ7Q31eey0xfX17XGthcHBhXjJ9XHJpZ2h0KVxyaWdodCleey0xfSBccHJvZF97aT0xfV5tIFxsZWZ0KFxtYXRoYmZ7SX0tcl97MWl9XGRmcmFje1xtYXRoYmZ7TH1cbWF0aGJme0N9XnstMX19e1xrYXBwYV4yfVxyaWdodClcY2RvdCAoXG1hdGhiZntDfVxtYXRoYmZ7VX1eaytcdGF1IFxtYXRoYmZ7Rn1ee2srMX0pLgpcZW5ke2VxdWF0aW9ufQpDb25zaWRlcmluZyB0aGUgcGFydGlhbCBmcmFjdGlvbiBkZWNvbXBvc2l0aW9uClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse2VxOnBhcnRpYWxfZnJhY3Rpb259Clx0YWd7MTB9ClxkZnJhY3tccHJvZF97aT0xfV5tICgxLXJfezFpfXgpfXtccHJvZF97aT0xfV5tICgxLXJfezFpfXgpK1xkZnJhY3tcdGF1IFxrYXBwYV57MlxiZXRhfX17XHRleHR0dHtmYWN0b3J9fSBccHJvZF97aj0xfV57bSsxfSAoMS1yX3syan14KX09XHN1bV97az0xfV57bSsxfSBhX2soeC1wX2spXnstMX0gKyByLApcZW5ke2VxdWF0aW9ufQpzY2hlbWUgXGVxcmVme2VxOmZpbmFsX3NjaGVtZX0gY2FuIGJlIGV4cHJlc3NlZCBhcwpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTpmaW5hbF9zY2hlbWUyfQpcdGFnezExfQpcbWF0aGJme1V9XntrKzF9ID0gXG1hdGhiZntDfV57LTF9XGxlZnQoXHN1bV97az0xfV57bSsxfSBhX2tcbGVmdCggXGRmcmFje1xtYXRoYmZ7TH1cbWF0aGJme0N9XnstMX19e1xrYXBwYV4yfS1wX2tcbWF0aGJme0l9XHJpZ2h0KV57LTF9ICsgclxtYXRoYmZ7SX1ccmlnaHQpIChcbWF0aGJme0N9XG1hdGhiZntVfV5rK1x0YXUgXG1hdGhiZntGfV57aysxfSkKXGVuZHtlcXVhdGlvbn0KCkluIHByYWN0aWNlLCBzaW5jZSB0aGUgcmF0aW9uYWwgZnVuY3Rpb24gaW4gXGVxcmVme2VxOnBhcnRpYWxfZnJhY3Rpb259IGlzIHByb3BlciwgdGhlcmUgaXMgbm8gcmVtYWluZGVyICRyJC4gTW9yZW92ZXIsIHNpbmNlICRcbGVmdCggXGRmcmFje1xtYXRoYmZ7TH1cbWF0aGJme0N9XnstMX19e1xrYXBwYV4yfS1wX2tcbWF0aGJme0l9XHJpZ2h0KV57LTF9ICA9IFxtYXRoYmZ7Q31cbGVmdCggXGRmcmFje1xtYXRoYmZ7TH19e1xrYXBwYV4yfS1wX2tcbWF0aGJme0N9XHJpZ2h0KV57LTF9JCwgd2UgaGF2ZSB0aGF0IFxlcXJlZntlcTpmaW5hbF9zY2hlbWUyfSBjYW4gYmUgcmV3cml0dGVuIGFzCgpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTpmaW5hbF9zY2hlbWUzfQpcdGFnezEyfQpcbWF0aGJme1V9XntrKzF9ID0gXG1hdGhiZntSfShcbWF0aGJme0N9XG1hdGhiZntVfV5rK1x0YXUgXG1hdGhiZntGfV57aysxfSksXHF1YWQgXG1hdGhiZntSfSA9IFxzdW1fe2s9MX1ee20rMX0gYV9rXGxlZnQoIFxkZnJhY3tcbWF0aGJme0x9fXtca2FwcGFeMn0tcF9rXG1hdGhiZntDfVxyaWdodCleey0xfS4KXGVuZHtlcXVhdGlvbn0KCiMjIE51bWVyaWNhbCBpbXBsZW1lbnRhdGlvbiB7I251bV9pbXBsZW1lbnRhdGlvbn0KCiMjIyBGdW5jdGlvbiBgbXkuZ2V0LnJvb3RzKClgCgpGb3IgZWFjaCByYXRpb25hbCBvcmRlciAkbSQgKDEsMiwzLDQpIGFuZCBzbW9vdGhuZXNzIHBhcmFtZXRlciAkXGJldGEkICg9ICRcYWxwaGEvMiQgd2l0aCAkXGFscGhhJCBiZXR3ZWVuIDAuNSBhbmQgMiksIGZ1bmN0aW9uIGBteS5nZXQucm9vdHMoKWAgKGFkYXB0ZWQgZnJvbSB0aGUgYHJTUERFYCBwYWNrYWdlKSByZXR1cm5zICRcdGV4dHR0e2ZhY3Rvcn0gPSBcZGZyYWN7Y19tfXtiX3ttKzF9fSQsIGFuZCB0aGUgcm9vdHMgJFx7cl97MWl9XH1fe2k9MX1ebSQgYW5kICRce3JfezJqfVx9X3tqPTF9XnttKzF9JC4KCmBgYHtyfQojIEZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIHJvb3RzIGFuZCBmYWN0b3IgZm9yIHRoZSByYXRpb25hbCBhcHByb3hpbWF0aW9uCm15LmdldC5yb290cyA8LSBmdW5jdGlvbihtLCAjIHJhdGlvbmFsIG9yZGVyLCBtID0gMSwgMiwgMywgb3IgNAogICAgICAgICAgICAgICAgICAgICAgICAgYmV0YSAjIHNtb290aG5lc3MgcGFyYW1ldGVyLCBiZXRhID0gYWxwaGEvMiB3aXRoIGFscGhhIGJldHdlZW4gMC41IGFuZCAyCiAgICAgICAgICAgICAgICAgICAgICAgICApIHsKICBtMXRhYmxlIDwtIHJTUERFOjo6bTF0YWJsZQogIG0ydGFibGUgPC0gclNQREU6OjptMnRhYmxlCiAgbTN0YWJsZSA8LSByU1BERTo6Om0zdGFibGUKICBtNHRhYmxlIDwtIHJTUERFOjo6bTR0YWJsZQogIG10IDwtIGdldChwYXN0ZTAoIm0iLCBtLCAidGFibGUiKSkKICByYiA8LSByZXAoMCwgbSArIDEpCiAgcmMgPC0gcmVwKDAsIG0pCiAgaWYobSA9PSAxKSB7CiAgICByYyA9IGFwcHJveChtdCRiZXRhLCBtdFtbcGFzdGUwKCJyYyIpXV0sIGJldGEpJHkKICB9IGVsc2UgewogICAgcmMgPSBzYXBwbHkoMTptLCBmdW5jdGlvbihpKSB7CiAgICAgIGFwcHJveChtdCRiZXRhLCBtdFtbcGFzdGUwKCJyYy4iLCBpKV1dLCBiZXRhKSR5CiAgICB9KQogIH0KICByYiA9IHNhcHBseSgxOihtKzEpLCBmdW5jdGlvbihpKSB7CiAgICBhcHByb3gobXQkYmV0YSwgbXRbW3Bhc3RlMCgicmIuIiwgaSldXSwgeG91dCA9IGJldGEpJHkKICB9KQogIGZhY3RvciA9IGFwcHJveChtdCRiZXRhLCBtdCRmYWN0b3IsIHhvdXQgPSBiZXRhKSR5CiAgcmV0dXJuKGxpc3QocGxfcm9vdHMgPSByYiwgIyByb290cyBce3JfezJqfVx9X3tqPTF9XnttKzF9CiAgICAgICAgICAgICAgcHJfcm9vdHMgPSByYywgIyByb290cyBce3JfezFpfVx9X3tpPTF9Xm0KICAgICAgICAgICAgICBmYWN0b3IgPSBmYWN0b3IgIyB0aGlzIGlzIGNfbS9iX3ttKzF9CiAgICAgICAgICAgICAgKSkKfQpgYGAKCiMjIyBGdW5jdGlvbiBgcG9seS5mcm9tLnJvb3RzKClgCgpGdW5jdGlvbiBgcG9seS5mcm9tLnJvb3RzKClgIGNvbXB1dGVzIHRoZSBjb2VmZmljaWVudHMgb2YgYSBwb2x5bm9taWFsIGZyb20gaXRzIHJvb3RzLgoKYGBge3J9CiMgRnVuY3Rpb24gdG8gY29tcHV0ZSBwb2x5bm9taWFsIGNvZWZmaWNpZW50cyBmcm9tIHJvb3RzCnBvbHkuZnJvbS5yb290cyA8LSBmdW5jdGlvbihyb290cykgewogIGNvZWYgPC0gMQogIGZvciAociBpbiByb290cykge2NvZWYgPC0gY29udm9sdmUoY29lZiwgYygxLCAtciksIHR5cGUgPSAib3BlbiIpfQogIHJldHVybihjb2VmKSAjIHJldHVybmVkIGluIGluY3JlYXNpbmcgb3JkZXIgbGlrZSBhK2J4K2N4XjIrLi4uCn0KYGBgCgojIyMgRnVuY3Rpb24gYGNvbXB1dGUucGFydGlhbC5mcmFjdGlvbi5wYXJhbSgpYAoKR2l2ZW4gYGZhY3RvcmAkPVx0ZXh0dHR7ZmFjdG9yfSA9IFxkZnJhY3tjX219e2Jfe20rMX19JCwgYHByX3Jvb3RzYCQ9XHtyX3sxaX1cfV97aT0xfV5tJCwgYHBsX3Jvb3RzYCQ9XHtyX3syan1cfV97aj0xfV57bSsxfSQsIGB0aW1lX3N0ZXBgJD1cdGF1JCwgYW5kIGBzY2FsaW5nYCQ9XGthcHBhXnsyXGJldGF9JCwgZnVuY3Rpb24gYGNvbXB1dGUucGFydGlhbC5mcmFjdGlvbi5wYXJhbSgpYCBjb21wdXRlcyB0aGUgcGFyYW1ldGVycyBmb3IgdGhlIHBhcnRpYWwgZnJhY3Rpb24gZGVjb21wb3NpdGlvbiBcZXFyZWZ7ZXE6cGFydGlhbF9mcmFjdGlvbn0uCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBjb21wdXRlIHRoZSBwYXJhbWV0ZXJzIGZvciB0aGUgcGFydGlhbCBmcmFjdGlvbiBkZWNvbXBvc2l0aW9uCmNvbXB1dGUucGFydGlhbC5mcmFjdGlvbi5wYXJhbSA8LSBmdW5jdGlvbihmYWN0b3IsICMgY19tL2Jfe20rMX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByX3Jvb3RzLCAjIHJvb3RzIFx7cl97MWl9XH1fe2k9MX1ebQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxfcm9vdHMsICMgcm9vdHMgXHtyX3syan1cfV97aj0xfV57bSsxfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZV9zdGVwLCAjIFx0YXUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxpbmcgIyBca2FwcGFeezJcYmV0YX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkgewogIHByX2NvZWYgPC0gYygwLCBwb2x5LmZyb20ucm9vdHMocHJfcm9vdHMpKQogIHBsX2NvZWYgPC0gcG9seS5mcm9tLnJvb3RzKHBsX3Jvb3RzKQogIGZhY3Rvcl9wcl9jb2VmIDwtIHByX2NvZWYKICBwcl9wbHVzX3BsX2NvZWYgPC0gZmFjdG9yX3ByX2NvZWYgKyAoKHNjYWxpbmcgKiB0aW1lX3N0ZXApL2ZhY3RvcikgKiBwbF9jb2VmCiAgcmVzIDwtIGdzaWduYWw6OnJlc2lkdWUoZmFjdG9yX3ByX2NvZWYsIHByX3BsdXNfcGxfY29lZikKICByZXR1cm4obGlzdChyID0gcmVzJHIsICMgcmVzaWR1ZXMgXHthX2tcfV97az0xfV57bSsxfQogICAgICAgICAgICAgIHAgPSByZXMkcCwgIyBwb2xlcyBce3Bfa1x9X3trPTF9XnttKzF9CiAgICAgICAgICAgICAgayA9IHJlcyRrICMgcmVtYWluZGVyIHIKICAgICAgICAgICAgICApKQp9CmBgYAoKIyMjIEZ1bmN0aW9uIGBteS5mcmFjdGlvbmFsLm9wZXJhdG9ycy5mcmFjKClgCgpHaXZlbiB0aGUgTGFwbGFjaWFuIG1hdHJpeCBgTGAsIHRoZSBzbW9vdGhuZXNzIHBhcmFtZXRlciBgYmV0YWAsIHRoZSBtYXNzIG1hdHJpeCBgQ2AgKG5vdCBsdW1wZWQpLCB0aGUgc2NhbGluZyBmYWN0b3IgYHNjYWxlLmZhY3RvcmAkPVxrYXBwYV4yJCwgdGhlIHJhdGlvbmFsIG9yZGVyIGBtYCwgYW5kIHRoZSB0aW1lIHN0ZXAgYHRpbWVfc3RlcGAkPVx0YXUkLCBmdW5jdGlvbiBgbXkuZnJhY3Rpb25hbC5vcGVyYXRvcnMuZnJhYygpYCBjb21wdXRlcyB0aGUgZnJhY3Rpb25hbCBvcGVyYXRvciBhbmQgcmV0dXJucyBhIGxpc3QgY29udGFpbmluZyB0aGUgbmVjZXNzYXJ5IG1hdHJpY2VzIGFuZCBwYXJhbWV0ZXJzIGZvciB0aGUgZnJhY3Rpb25hbCBkaWZmdXNpb24gZXF1YXRpb24uCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBjb21wdXRlIHRoZSBmcmFjdGlvbmFsIG9wZXJhdG9yCm15LmZyYWN0aW9uYWwub3BlcmF0b3JzLmZyYWMgPC0gZnVuY3Rpb24oTCwgIyBMYXBsYWNpYW4gbWF0cml4CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmV0YSwgIyBzbW9vdGhuZXNzIHBhcmFtZXRlciBiZXRhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQywgIyBtYXNzIG1hdHJpeCAobm90IGx1bXBlZCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZS5mYWN0b3IsICMgc2NhbGluZyBwYXJhbWV0ZXIgPSBrYXBwYV4yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbSA9IDEsICMgcmF0aW9uYWwgb3JkZXIsIG0gPSAxLCAyLCAzLCBvciA0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZV9zdGVwICMgdGltZSBzdGVwID0gdGF1CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSB7CiAgSSA8LSBNYXRyaXg6OkRpYWdvbmFsKGRpbShDKVsxXSkKICBMIDwtIEwgLyBzY2FsZS5mYWN0b3IKICBpZihiZXRhID09IDEpewogICAgTCA8LSBMICogc2NhbGUuZmFjdG9yXmJldGEKICAgIHJldHVybihsaXN0KEMgPSBDLCAjIG1hc3MgbWF0cml4CiAgICAgICAgICAgICAgICBMID0gTCwgIyBMYXBsYWNpYW4gbWF0cml4IHNjYWxlZAogICAgICAgICAgICAgICAgbSA9IG0sICMgcmF0aW9uYWwgb3JkZXIKICAgICAgICAgICAgICAgIGJldGEgPSBiZXRhLCAjIHNtb290aG5lc3MgcGFyYW1ldGVyCiAgICAgICAgICAgICAgICBMSFMgPSBDICsgdGltZV9zdGVwICogTCAjIGxlZnQtaGFuZCBzaWRlIG9mIHRoZSBsaW5lYXIgc3lzdGVtCiAgICAgICAgICAgICAgICApKQogIH0gZWxzZSB7CiAgICBzY2FsaW5nIDwtIHNjYWxlLmZhY3Rvcl5iZXRhCiAgICByb290cyA8LSBteS5nZXQucm9vdHMobSwgYmV0YSkKICAgIHBvbGVzX3JzX2sgPC0gY29tcHV0ZS5wYXJ0aWFsLmZyYWN0aW9uLnBhcmFtKHJvb3RzJGZhY3Rvciwgcm9vdHMkcHJfcm9vdHMsIHJvb3RzJHBsX3Jvb3RzLCB0aW1lX3N0ZXAsIHNjYWxpbmcpCgogICAgcGFydGlhbF9mcmFjdGlvbl90ZXJtcyA8LSBsaXN0KCkKICAgIGZvciAoaSBpbiAxOihtKzEpKSB7CiAgICAgICMgSGVyZSBpcyB3aGVyZSB0aGUgdGVybXMgaW4gdGhlIHN1bSBpbiBlcSAxMiBhcmUgY29tcHV0ZWQKICAgICAgcGFydGlhbF9mcmFjdGlvbl90ZXJtc1tbaV1dIDwtIChMIC0gcG9sZXNfcnNfayRwW2ldICogQykjL3BvbGVzX3JzX2skcltpXQogICAgICB9CiAgICByZXR1cm4obGlzdChDID0gQywgIyBtYXNzIG1hdHJpeAogICAgICAgICAgICAgICAgTCA9IEwsICMgTGFwbGFjaWFuIG1hdHJpeCBzY2FsZWQKICAgICAgICAgICAgICAgIG0gPSBtLCAjIHJhdGlvbmFsIG9yZGVyCiAgICAgICAgICAgICAgICBiZXRhID0gYmV0YSwgIyBzbW9vdGhuZXNzIHBhcmFtZXRlcgogICAgICAgICAgICAgICAgcGFydGlhbF9mcmFjdGlvbl90ZXJtcyA9IHBhcnRpYWxfZnJhY3Rpb25fdGVybXMsICMgcGFydGlhbCBmcmFjdGlvbiB0ZXJtcwogICAgICAgICAgICAgICAgcmVzaWR1ZXMgPSBwb2xlc19yc19rJHIgIyByZXNpZHVlcyBce2Ffa1x9X3trPTF9XnttKzF9CiAgICAgICAgICAgICAgICApKQogIH0KfQpgYGAKCiMjIyBGdW5jdGlvbiBgbXkuc29sdmVyLmZyYWMoKWAKCkdpdmVuIHRoZSBvYmplY3QgcmV0dXJuZWQgYnkgYG15LmZyYWN0aW9uYWwub3BlcmF0b3JzLmZyYWMoKWAgYW5kIGEgdmVjdG9yIGB2YCwgZnVuY3Rpb24gYG15LnNvbHZlci5mcmFjKClgIHNvbHZlcyB0aGUgbGluZWFyIFxlcXJlZntlcTpmaW5hbF9zY2hlbWUyfSBmb3IgdGhlIHZlY3RvciBgdmAuIElmIGBiZXRhID0gMWAsIGl0IHNvbHZlcyB0aGUgc3lzdGVtIGRpcmVjdGx5OyBvdGhlcndpc2UsIGl0IHVzZXMgdGhlIHBhcnRpYWwgZnJhY3Rpb24gZGVjb21wb3NpdGlvbi4KCmBgYHtyfQojIEZ1bmN0aW9uIHRvIHNvbHZlIHRoZSBpdGVyYXRpb24KbXkuc29sdmVyLmZyYWMgPC0gZnVuY3Rpb24ob2JqLCAjIG9iamVjdCByZXR1cm5lZCBieSBteS5mcmFjdGlvbmFsLm9wZXJhdG9ycy5mcmFjKCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgdiAjIHZlY3RvciB0byBiZSBzb2x2ZWQgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICl7CiAgYmV0YSA8LSBvYmokYmV0YQogIG0gPC0gb2JqJG0KICBpZiAoYmV0YSA9PSAxKXsKICAgIHJldHVybihzb2x2ZShvYmokTEhTLCB2KSAjIHNvbHZlIHRoZSBsaW5lYXIgc3lzdGVtIGRpcmVjdGx5IGZvciBiZXRhID0gMQogICAgICAgICAgICkKICB9IGVsc2UgewogICAgcGFydGlhbF9mcmFjdGlvbl90ZXJtcyA8LSBvYmokcGFydGlhbF9mcmFjdGlvbl90ZXJtcwogICAgcmVzaWR1ZXMgPC0gb2JqJHJlc2lkdWVzCiAgICBvdXRwdXQgPC0gdiowCiAgICBmb3IgKGkgaW4gMToobSsxKSkge291dHB1dCA8LSBvdXRwdXQgKyByZXNpZHVlc1tpXSAqIHNvbHZlKHBhcnRpYWxfZnJhY3Rpb25fdGVybXNbW2ldXSwgdil9CiAgICByZXR1cm4ob3V0cHV0ICMgc29sdmUgdGhlIGxpbmVhciBzeXN0ZW0gdXNpbmcgdGhlIHBhcnRpYWwgZnJhY3Rpb24gZGVjb21wb3NpdGlvbgogICAgICAgICAgICkKICB9Cn0KCgojIG15LnNvbHZlci5mcmFjIDwtIGZ1bmN0aW9uKG9iaiwgdikgewojICAgYmV0YSA8LSBvYmokYmV0YQojICAgbSA8LSBvYmokbQojICAgCiMgICBpZiAoYmV0YSA9PSAxKSB7CiMgICAgIHJldHVybihzb2x2ZShvYmokTEhTLCB2KSkKIyAgIH0gCiMgICAKIyAgIHBhcnRpYWxfZnJhY3Rpb25fdGVybXMgPC0gb2JqJHBhcnRpYWxfZnJhY3Rpb25fdGVybXMKIyAgIHJlc2lkdWVzIDwtIG9iaiRyZXNpZHVlcwojICAgCiMgICAjIC0tLSBQQVJBTExFTCBQQVJUIC0tLQojICAgcmVzdWx0cyA8LSBwYXJhbGxlbDo6bWNsYXBwbHkoCiMgICAgIDE6KG0rMSksCiMgICAgIGZ1bmN0aW9uKGkpIHJlc2lkdWVzW2ldICogc29sdmUocGFydGlhbF9mcmFjdGlvbl90ZXJtc1tbaV1dLCB2KSwKIyAgICAgbWMuY29yZXMgPSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSAgCiMgICApCiMgICAKIyAgIHJldHVybihSZWR1Y2UoYCtgLCByZXN1bHRzKSkKIyB9CgpgYGAKCgojIyMgRnVuY3Rpb24gYHNvbHZlX2ZyYWN0aW9uYWxfZXZvbHV0aW9uKClgCgpHaXZlbiB0aGUgZnJhY3Rpb25hbCBvcGVyYXRvciBvYmplY3QgYG15X29wX2ZyYWNgLCBhIHRpbWUgc3RlcCBgdGltZV9zdGVwYCwgYSBzZXF1ZW5jZSBvZiB0aW1lIHBvaW50cyBgdGltZV9zZXFgLCBhbiBpbml0aWFsIHZhbHVlIGB2YWxfYXRfMGAsIGFuZCB0aGUgcmlnaHQtaGFuZCBzaWRlIG1hdHJpeCBgUkhTVGAsIGZ1bmN0aW9uIGBzb2x2ZV9mcmFjdGlvbmFsX2V2b2x1dGlvbigpYCBjb21wdXRlcyB0aGUgc29sdXRpb24gdG8gdGhlIGZyYWN0aW9uYWwgZGlmZnVzaW9uIGVxdWF0aW9uIGF0IGVhY2ggdGltZSBzdGVwIHVzaW5nIHNjaGVtZSBcZXFyZWZ7ZXE6ZmluYWxfc2NoZW1lMn0uCgoKYGBge3J9CnNvbHZlX2ZyYWN0aW9uYWxfZXZvbHV0aW9uIDwtIGZ1bmN0aW9uKG15X29wX2ZyYWMsIHRpbWVfc3RlcCwgdGltZV9zZXEsIHZhbF9hdF8wLCBSSFNUKSB7CiAgQ0MgPC0gbXlfb3BfZnJhYyRDCiAgU09MIDwtIG1hdHJpeChOQSwgbnJvdyA9IG5yb3coQ0MpLCBuY29sID0gbGVuZ3RoKHRpbWVfc2VxKSkKICBTT0xbLCAxXSA8LSB2YWxfYXRfMAogIGZvciAoayBpbiAxOihsZW5ndGgodGltZV9zZXEpIC0gMSkpIHsKICAgIHJocyA8LSBDQyAlKiUgU09MWywga10gKyB0aW1lX3N0ZXAgKiBSSFNUWywgayArIDFdCiAgICBTT0xbLCBrICsgMV0gPC0gYXMubWF0cml4KG15LnNvbHZlci5mcmFjKG15X29wX2ZyYWMsIHJocykpCiAgfQogIHJldHVybihTT0wpCn0KYGBgCgoKIyMgQXV4aWxpYXJ5IGZ1bmN0aW9ucyB7I2F1eGlsaWFyeV9mdW5jdGlvbnN9CgojIyMgRnVuY3Rpb24gYGdldHMuZ3JhcGgudGFkcG9sZSgpYAoKR2l2ZW4gYSBtZXNoIHNpemUgYGhgLCBmdW5jdGlvbiBgZ2V0cy5ncmFwaC50YWRwb2xlKClgIGJ1aWxkcyBhIHRhZHBvbGUgZ3JhcGggYW5kIGNyZWF0ZXMgYSBtZXNoLgoKCmBgYHtyfQojIEZ1bmN0aW9uIHRvIGJ1aWxkIGEgdGFkcG9sZSBncmFwaCBhbmQgY3JlYXRlIGEgbWVzaApnZXRzLmdyYXBoLnRhZHBvbGUgPC0gZnVuY3Rpb24oaCl7CiAgZWRnZTEgPC0gcmJpbmQoYygwLDApLGMoMSwwKSkKICB0aGV0YSA8LSBzZXEoZnJvbT0tcGksdG89cGksbGVuZ3RoLm91dCA9IDEwMDAwKQogIGVkZ2UyIDwtIGNiaW5kKDErMS9waStjb3ModGhldGEpL3BpLHNpbih0aGV0YSkvcGkpCiAgZWRnZXMgPC0gbGlzdChlZGdlMSwgZWRnZTIpCiAgZ3JhcGggPC0gbWV0cmljX2dyYXBoJG5ldyhlZGdlcyA9IGVkZ2VzLCB2ZXJib3NlID0gMCkKICBncmFwaCRzZXRfbWFudWFsX2VkZ2VfbGVuZ3RocyhlZGdlX2xlbmd0aHMgPSBjKDEsMikpCiAgZ3JhcGgkYnVpbGRfbWVzaChoID0gaCkKICByZXR1cm4oZ3JhcGgpCn0KYGBgCgoKIyMjIEZ1bmN0aW9uIGB0YWRwb2xlLmVpZygpYAoKR2l2ZW4gYSBtb2RlIG51bWJlciBga2AgYW5kIGEgdGFkcG9sZSBncmFwaCBgZ3JhcGhgLCBmdW5jdGlvbiBgdGFkcG9sZS5laWcoKWAgY29tcHV0ZXMgdGhlIGVpZ2VucGFpcnMgb2YgdGhlIHRhZHBvbGUgZ3JhcGguCgoKYGBge3J9CiMgRnVuY3Rpb24gdG8gY29tcHV0ZSB0aGUgZWlnZW5mdW5jdGlvbnMgb2YgdGhlIHRhZHBvbGUgZ3JhcGgKdGFkcG9sZS5laWcgPC0gZnVuY3Rpb24oayxncmFwaCl7CngxIDwtIGMoMCxncmFwaCRnZXRfZWRnZV9sZW5ndGhzKClbMV0qZ3JhcGgkbWVzaCRQdEVbZ3JhcGgkbWVzaCRQdEVbLDFdPT0xLDJdKSAKeDIgPC0gYygwLGdyYXBoJGdldF9lZGdlX2xlbmd0aHMoKVsyXSpncmFwaCRtZXNoJFB0RVtncmFwaCRtZXNoJFB0RVssMV09PTIsMl0pIAoKaWYoaz09MCl7IAogIGYuZTEgPC0gcmVwKDEsbGVuZ3RoKHgxKSkgCiAgZi5lMiA8LSByZXAoMSxsZW5ndGgoeDIpKSAKICBmMSA9IGMoZi5lMVsxXSxmLmUyWzFdLGYuZTFbLTFdLCBmLmUyWy0xXSkgCiAgZiA9IGxpc3QocGhpPWYxL3NxcnQoMykpIAogIAp9IGVsc2UgewogIGYuZTEgPC0gLTIqc2luKHBpKmsqMS8yKSpjb3MocGkqayp4MS8yKSAKICBmLmUyIDwtIHNpbihwaSprKngyLzIpICAgICAgICAgICAgICAgICAgCiAgCiAgZjEgPSBjKGYuZTFbMV0sZi5lMlsxXSxmLmUxWy0xXSwgZi5lMlstMV0pIAogIAogIGlmKChrICUlIDIpPT0xKXsgCiAgICBmID0gbGlzdChwaGk9ZjEvc3FydCgzKSkgCiAgfSBlbHNlIHsgCiAgICBmLmUxIDwtICgtMSlee2svMn0qY29zKHBpKmsqeDEvMikKICAgIGYuZTIgPC0gY29zKHBpKmsqeDIvMikKICAgIGYyID0gYyhmLmUxWzFdLGYuZTJbMV0sZi5lMVstMV0sZi5lMlstMV0pIAogICAgZiA8LSBsaXN0KHBoaT1mMSxwc2k9ZjIvc3FydCgzLzIpKQogIH0KfQpyZXR1cm4oZikKfQpgYGAKCiMjIyBGdW5jdGlvbiBgZ2V0cy5laWdlbi5wYXJhbXMoKWAKCkdpdmVuIGEgZmluaXRlIG51bWJlciBvZiBtb2RlcyBgTl9maW5pdGVgLCBhIHNjYWxpbmcgcGFyYW1ldGVyIGBrYXBwYWAsIGEgc21vb3RobmVzcyBwYXJhbWV0ZXIgYGFscGhhYCwgYW5kIGEgdGFkcG9sZSBncmFwaCBgZ3JhcGhgLCBmdW5jdGlvbiBgZ2V0cy5laWdlbi5wYXJhbXMoKWAgY29tcHV0ZXMgYEVJR0VOVkFMX0FMUEhBYCAoYSB2ZWN0b3Igd2l0aCBlbnRyaWVzICRcbGFtYmRhX2pee1xhbHBoYS8yfSQpLCBgRUlHRU5WQUxfTUlOVVNfQUxQSEFgIChhIHZlY3RvciB3aXRoIGVudHJpZXMgJFxsYW1iZGFfal57LVxhbHBoYS8yfSQpLCBhbmQgYEVJR0VORlVOYCAoYSBtYXRyaXggd2l0aCBjb2x1bW5zICRlX2okIG9uIHRoZSBtZXNoIG9mIGBncmFwaGApLgoKCmBgYHtyfQojIEZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIGVpZ2VucGFpcnMgb2YgdGhlIHRhZHBvbGUgZ3JhcGgKZ2V0cy5laWdlbi5wYXJhbXMgPC0gZnVuY3Rpb24oTl9maW5pdGUgPSA0LCBrYXBwYSA9IDEsIGFscGhhID0gMC41LCBncmFwaCl7CiAgRUlHRU5WQUwgPC0gTlVMTAogIEVJR0VOVkFMX0FMUEhBIDwtIE5VTEwKICBFSUdFTlZBTF9NSU5VU19BTFBIQSA8LSBOVUxMCiAgRUlHRU5GVU4gPC0gTlVMTAogIElOREVYIDwtIE5VTEwKICBmb3IgKGogaW4gMDpOX2Zpbml0ZSkgewogICAgbGFtYmRhX2ogPC0ga2FwcGFeMiArIChqKnBpLzIpXjIKICAgIGxhbWJkYV9qX2FscGhhX2hhbGYgPC0gbGFtYmRhX2peKGFscGhhLzIpCiAgICBsYW1iZGFfal9taW51c19hbHBoYV9oYWxmIDwtIGxhbWJkYV9qXigtYWxwaGEvMikKICAgIGVfaiA8LSB0YWRwb2xlLmVpZyhqLGdyYXBoKSRwaGkKICAgIEVJR0VOVkFMIDwtIGMoRUlHRU5WQUwsIGxhbWJkYV9qKQogICAgRUlHRU5WQUxfQUxQSEEgPC0gYyhFSUdFTlZBTF9BTFBIQSwgbGFtYmRhX2pfYWxwaGFfaGFsZikgIAogICAgRUlHRU5WQUxfTUlOVVNfQUxQSEEgPC0gYyhFSUdFTlZBTF9NSU5VU19BTFBIQSwgbGFtYmRhX2pfbWludXNfYWxwaGFfaGFsZikKICAgIEVJR0VORlVOIDwtIGNiaW5kKEVJR0VORlVOLCBlX2opCiAgICBJTkRFWCA8LSBjKElOREVYLCBqKQogICAgaWYgKGo+MCAmJiAoaiAlJSAyID09IDApKSB7CiAgICAgIGxhbWJkYV9qIDwtIGthcHBhXjIgKyAoaipwaS8yKV4yCiAgICAgIGxhbWJkYV9qX2FscGhhX2hhbGYgPC0gbGFtYmRhX2peKGFscGhhLzIpCiAgICAgIGxhbWJkYV9qX21pbnVzX2FscGhhX2hhbGYgPC0gbGFtYmRhX2peKC1hbHBoYS8yKQogICAgICBlX2ogPC0gdGFkcG9sZS5laWcoaixncmFwaCkkcHNpCiAgICAgIEVJR0VOVkFMIDwtIGMoRUlHRU5WQUwsIGxhbWJkYV9qKQogICAgICBFSUdFTlZBTF9BTFBIQSA8LSBjKEVJR0VOVkFMX0FMUEhBLCBsYW1iZGFfal9hbHBoYV9oYWxmKSAgICAKICAgICAgRUlHRU5WQUxfTUlOVVNfQUxQSEEgPC0gYyhFSUdFTlZBTF9NSU5VU19BTFBIQSwgbGFtYmRhX2pfbWludXNfYWxwaGFfaGFsZikKICAgICAgRUlHRU5GVU4gPC0gY2JpbmQoRUlHRU5GVU4sIGVfaikKICAgICAgSU5ERVggPC0gYyhJTkRFWCwgaiswLjEpCiAgICAgIH0KICAgIH0KICByZXR1cm4obGlzdChFSUdFTlZBTCA9IEVJR0VOVkFMLAogICAgICAgICAgICAgIEVJR0VOVkFMX0FMUEhBID0gRUlHRU5WQUxfQUxQSEEsIAogICAgICAgICAgICAgIEVJR0VOVkFMX01JTlVTX0FMUEhBID0gRUlHRU5WQUxfTUlOVVNfQUxQSEEsCiAgICAgICAgICAgICAgRUlHRU5GVU4gPSBFSUdFTkZVTiwKICAgICAgICAgICAgICBJTkRFWCA9IElOREVYKSkKfQpgYGAKCgojIyMgRnVuY3Rpb24gYGNvbnN0cnVjdF9waWVjZXdpc2VfcHJvamVjdGlvbigpYCB7I2NvbnN0cnVjdF9waWVjZXdpc2VfcHJvamVjdGlvbn0KCkdpdmVuIGEgbWF0cml4IGBwcm9qZWN0ZWRfVV9hcHByb3hgIHdpdGggYXBwcm94aW1hdGVkIHZhbHVlcyBhdCBkaXNjcmV0ZSB0aW1lIHBvaW50cywgYSBzZXF1ZW5jZSBvZiB0aW1lIHBvaW50cyBgdGltZV9zZXFgLCBhbmQgYW4gZXh0ZW5kZWQgc2VxdWVuY2Ugb2YgdGltZSBwb2ludHMgYG92ZXJraWxsX3RpbWVfc2VxYCwgZnVuY3Rpb24gYGNvbnN0cnVjdF9waWVjZXdpc2VfcHJvamVjdGlvbigpYCBjb25zdHJ1Y3RzIGEgcGllY2V3aXNlIGNvbnN0YW50IHByb2plY3Rpb24gb2YgdGhlIGFwcHJveGltYXRlZCB2YWx1ZXMgb3ZlciB0aGUgZXh0ZW5kZWQgdGltZSBzZXF1ZW5jZS4KCmBgYHtyfQojIEZ1bmN0aW9uIHRvIGNvbnN0cnVjdCBhIHBpZWNld2lzZSBjb25zdGFudCBwcm9qZWN0aW9uIG9mIGFwcHJveGltYXRlZCB2YWx1ZXMKY29uc3RydWN0X3BpZWNld2lzZV9wcm9qZWN0aW9uIDwtIGZ1bmN0aW9uKHByb2plY3RlZF9VX2FwcHJveCwgdGltZV9zZXEsIG92ZXJraWxsX3RpbWVfc2VxKSB7CiAgcHJvamVjdGVkX1VfcGllY2V3aXNlIDwtIG1hdHJpeChOQSwgbnJvdyA9IG5yb3cocHJvamVjdGVkX1VfYXBwcm94KSwgbmNvbCA9IGxlbmd0aChvdmVya2lsbF90aW1lX3NlcSkpCiAgCiAgIyBBc3NpZ24gdmFsdWUgYXQgdCA9IDAKICBwcm9qZWN0ZWRfVV9waWVjZXdpc2VbLCB3aGljaChvdmVya2lsbF90aW1lX3NlcSA9PSAwKV0gPC0gcHJvamVjdGVkX1VfYXBwcm94WywgMV0KICAKICAjIEFzc2lnbiB2YWx1ZXMgZm9yIGludGVydmFscyAodF97ay0xfSwgdF9rXQogIGZvciAoayBpbiAyOmxlbmd0aCh0aW1lX3NlcSkpIHsKICAgIGlkeHMgPC0gd2hpY2gob3ZlcmtpbGxfdGltZV9zZXEgPiB0aW1lX3NlcVtrIC0gMV0gJiBvdmVya2lsbF90aW1lX3NlcSA8PSB0aW1lX3NlcVtrXSkKICAgIHByb2plY3RlZF9VX3BpZWNld2lzZVssIGlkeHNdIDwtIHByb2plY3RlZF9VX2FwcHJveFssIGtdCiAgfQogIAogIHJldHVybihwcm9qZWN0ZWRfVV9waWVjZXdpc2UpCn0KYGBgCgojIyMgRnVuY3Rpb24gYGNvbXB1dGVfZ3VpZGluZ19saW5lcygpYAoKR2l2ZW4gYSB2ZWN0b3IgYHhfYXhpc192ZWN0b3JgLCBhIG1hdHJpeCBgZXJyb3JzYCB3aXRoIGVycm9yIHZhbHVlcywgYSB2ZWN0b3IgYHRoZW9yZXRpY2FsX3JhdGVzYCB3aXRoIHRoZW9yZXRpY2FsIGNvbnZlcmdlbmNlIHJhdGVzLCBhbmQgYSBmdW5jdGlvbiBgbGluZV9lcXVhdGlvbl9mdW5gIChlaXRoZXIgYGxvZ2xvZ19saW5lX2VxdWF0aW9uYCBvciBgZXhwX2xpbmVfZXF1YXRpb25gKSwgZnVuY3Rpb24gYGNvbXB1dGVfZ3VpZGluZ19saW5lcygpYCBjb21wdXRlcyBndWlkaW5nIGxpbmVzIGZvciBjb252ZXJnZW5jZSBwbG90cy4KCmBgYHtyfQpsb2dsb2dfbGluZV9lcXVhdGlvbiA8LSBmdW5jdGlvbih4MSwgeTEsIHNsb3BlKSB7CiAgYiA8LSBsb2cxMCh5MSAvICh4MSBeIHNsb3BlKSkKICAKICBmdW5jdGlvbih4KSB7CiAgICAoeCBeIHNsb3BlKSAqICgxMCBeIGIpCiAgfQp9CmV4cF9saW5lX2VxdWF0aW9uIDwtIGZ1bmN0aW9uKHgxLCB5MSwgc2xvcGUpIHsKICBsbkMgPC0gbG9nKHkxKSAtIHNsb3BlICogeDEKICAKICBmdW5jdGlvbih4KSB7CiAgICBleHAobG5DICsgc2xvcGUgKiB4KQogIH0KfQpjb21wdXRlX2d1aWRpbmdfbGluZXMgPC0gZnVuY3Rpb24oeF9heGlzX3ZlY3RvciwgZXJyb3JzLCB0aGVvcmV0aWNhbF9yYXRlcywgbGluZV9lcXVhdGlvbl9mdW4pIHsKICBndWlkaW5nX2xpbmVzIDwtIG1hdHJpeChOQSwgbnJvdyA9IGxlbmd0aCh4X2F4aXNfdmVjdG9yKSwgbmNvbCA9IGxlbmd0aCh0aGVvcmV0aWNhbF9yYXRlcykpCiAgCiAgZm9yIChqIGluIHNlcV9hbG9uZyh0aGVvcmV0aWNhbF9yYXRlcykpIHsKICAgIGd1aWRpbmdfbGluZXNfYXV4IDwtIG1hdHJpeChOQSwgbnJvdyA9IGxlbmd0aCh4X2F4aXNfdmVjdG9yKSwgbmNvbCA9IGxlbmd0aCh4X2F4aXNfdmVjdG9yKSkKICAgIAogICAgZm9yIChrIGluIHNlcV9hbG9uZyh4X2F4aXNfdmVjdG9yKSkgewogICAgICBwb2ludF94MSA8LSB4X2F4aXNfdmVjdG9yW2tdCiAgICAgIHBvaW50X3kxIDwtIGVycm9yc1trLCBqXQogICAgICBzbG9wZSA8LSB0aGVvcmV0aWNhbF9yYXRlc1tqXQogICAgICAKICAgICAgbGluZSA8LSBsaW5lX2VxdWF0aW9uX2Z1bih4MSA9IHBvaW50X3gxLCB5MSA9IHBvaW50X3kxLCBzbG9wZSA9IHNsb3BlKQogICAgICBndWlkaW5nX2xpbmVzX2F1eFssIGtdIDwtIGxpbmUoeF9heGlzX3ZlY3RvcikKICAgIH0KICAgIAogICAgZ3VpZGluZ19saW5lc1ssIGpdIDwtIHJvd01lYW5zKGd1aWRpbmdfbGluZXNfYXV4KQogIH0KICAKICByZXR1cm4oZ3VpZGluZ19saW5lcykKfQpgYGAKCgoKIyMjIEZ1bmN0aW9ucyBmb3IgY29tcHV0aW5nIGFuIGV4YWN0IHNvbHV0aW9uIHRvIHRoZSBmcmFjdGlvbmFsIGRpZmZ1c2lvbiBlcXVhdGlvbiB7I2V4YWN0X3NvbHV0aW9ufQoKQmVsb3cgd2UgcHJlc2VudCBjbG9zZWQtZm9ybSBleHByZXNzaW9ucyBmb3IgJFxkaXNwbGF5c3R5bGUgR19qKHQpPSBcaW50XzBedCBlXntcbGFtYmRhXntcYWxwaGEvMn1fanJ9ZyhyKWRyJCBjb3JyZXNwb25kaW5nIHRvIHNvbWUgY2hvaWNlcyBvZiAkZyhyKSQuCgpcYmVnaW57YWxpZ25lZH0KZyhyKSAmPSBBZV57LVxsYW1iZGFee1xhbHBoYS8yfV9qIHJ9ICZcaW1wbGllc1xxdWFkIEdfaih0KSAmPSBBdCwgXHF1YWQgQVxpblxtYXRoYmJ7Un0gXFxbMS41ZXhdCmcocikgJj0gQWVee1xtdSByfSAmXGltcGxpZXNccXVhZCBHX2oodCkgJj0gQSBcZnJhY3tlXnsoXGxhbWJkYV57XGFscGhhLzJ9X2orXG11KXR9IC0gMX17XGxhbWJkYV57XGFscGhhLzJ9X2ogKyBcbXV9LCBccXVhZCAtXGxhbWJkYV57XGFscGhhLzJ9X2ogXG5lIFxtdSBcaW4gXG1hdGhiYntSfSBcXFsxLjVleF0KZyhyKSAmPSBBcl5uICZcaW1wbGllc1xxdWFkIEdfaih0KSAmPSBBXGZyYWN7KC0xKV57bisxfSBuIX17KFxsYW1iZGFfal57XGFscGhhLzJ9KV57bisxfX0gXGxlZnQoMSAtIGVee1xsYW1iZGFfal57XGFscGhhLzJ9IHR9IFxzdW1fe2s9MH1ebiBcZnJhY3soLVxsYW1iZGFfal57XGFscGhhLzJ9IHQpXmt9e2shfSBccmlnaHQpLCBccXVhZCBuPTAsMSxcZG90cyBcXFsxLjVleF0KZyhyKSAmPSBBXHNpbihcb21lZ2EgcikgJlxpbXBsaWVzXHF1YWQgR19qKHQpICY9IEEgXGZyYWN7ZV57XGxhbWJkYV9qXntcYWxwaGEvMn0gdH0gXGxlZnQoIFxsYW1iZGFfal57XGFscGhhLzJ9IFxzaW4oXG9tZWdhIHQpIC0gXG9tZWdhIFxjb3MoXG9tZWdhIHQpIFxyaWdodCkgKyBcb21lZ2F9eyhcbGFtYmRhX2pee1xhbHBoYS8yfSleMiArIFxvbWVnYV4yfSwgXHF1YWQgXG9tZWdhIFxpbiBcbWF0aGJie1J9IFxcWzEuNWV4XQpnKHIpICY9IEFcY29zKFx0aGV0YSByKSAmXGltcGxpZXNccXVhZCBHX2oodCkgJj0gQSBcZnJhY3tlXntcbGFtYmRhX2pee1xhbHBoYS8yfSB0fSBcbGVmdCggXGxhbWJkYV9qXntcYWxwaGEvMn0gXGNvcyhcdGhldGEgdCkgKyBcdGhldGEgXHNpbihcdGhldGEgdCkgXHJpZ2h0KSAtIFxsYW1iZGFfal57XGFscGhhLzJ9fXsoXGxhbWJkYV9qXntcYWxwaGEvMn0pXjIgKyBcdGhldGFeMn0sIFxxdWFkIFx0aGV0YSBcaW4gXG1hdGhiYntSfQpcZW5ke2FsaWduZWR9CgoKYGBge3J9CiMgRnVuY3Rpb25zIHRvIGNvbXB1dGUgdGhlIGV4YWN0IHNvbHV0aW9uIHRvIHRoZSBmcmFjdGlvbmFsIGRpZmZ1c2lvbiBlcXVhdGlvbgpnX2xpbmVhciA8LSBmdW5jdGlvbihyLCBBLCBsYW1iZGFfal9hbHBoYV9oYWxmKSB7CiAgcmV0dXJuKEEgKiBleHAoLWxhbWJkYV9qX2FscGhhX2hhbGYgKiByKSkKICB9CkdfbGluZWFyIDwtIGZ1bmN0aW9uKHQsIEEpIHsKICByZXR1cm4oQSAqIHQpCiAgfQpnX2V4cCA8LSBmdW5jdGlvbihyLCBBLCBtdSkgewogIHJldHVybihBICogZXhwKG11ICogcikpCiAgfQpHX2V4cCA8LSBmdW5jdGlvbih0LCBBLCBsYW1iZGFfal9hbHBoYV9oYWxmLCBtdSkgewogIGV4cG9uZW50IDwtIGxhbWJkYV9qX2FscGhhX2hhbGYgKyBtdQogIHJldHVybihBICogKGV4cChleHBvbmVudCAqIHQpIC0gMSkgLyBleHBvbmVudCkKICB9CmdfcG9seSA8LSBmdW5jdGlvbihyLCBBLCBuKSB7CiAgcmV0dXJuKEEgKiByXm4pCn0KR19wb2x5IDwtIGZ1bmN0aW9uKHQsIEEsIGxhbWJkYV9qX2FscGhhX2hhbGYsIG4pIHsKICB0IDwtIGFzLnZlY3Rvcih0KQogIGtfdmFscyA8LSAwOm4KICBzdW1fdGVybSA8LSBzYXBwbHkodCwgZnVuY3Rpb24odHQpIHsKICAgIHN1bSgoKC1sYW1iZGFfal9hbHBoYV9oYWxmICogdHQpXmtfdmFscykgLyBmYWN0b3JpYWwoa192YWxzKSkKICB9KQogIGNvZWZmIDwtICgoLTEpXihuICsgMSkpICogZmFjdG9yaWFsKG4pIC8gKGxhbWJkYV9qX2FscGhhX2hhbGZeKG4gKyAxKSkKICByZXR1cm4oQSAqIGNvZWZmICogKDEgLSBleHAobGFtYmRhX2pfYWxwaGFfaGFsZiAqIHQpICogc3VtX3Rlcm0pKQp9Cmdfc2luIDwtIGZ1bmN0aW9uKHIsIEEsIG9tZWdhKSB7CiAgcmV0dXJuKEEgKiBzaW4ob21lZ2EgKiByKSkKfQpHX3NpbiA8LSBmdW5jdGlvbih0LCBBLCBsYW1iZGFfal9hbHBoYV9oYWxmLCBvbWVnYSkgewogIGRlbm9tIDwtIGxhbWJkYV9qX2FscGhhX2hhbGZeMiArIG9tZWdhXjIKICBudW1lcmF0b3IgPC0gZXhwKGxhbWJkYV9qX2FscGhhX2hhbGYgKiB0KSAqIChsYW1iZGFfal9hbHBoYV9oYWxmICogc2luKG9tZWdhICogdCkgLSBvbWVnYSAqIGNvcyhvbWVnYSAqIHQpKSArIG9tZWdhCiAgcmV0dXJuKEEgKiBudW1lcmF0b3IgLyBkZW5vbSkKfQpnX2NvcyA8LSBmdW5jdGlvbihyLCBBLCB0aGV0YSkgewogIHJldHVybihBICogY29zKHRoZXRhICogcikpIAp9CkdfY29zIDwtIGZ1bmN0aW9uKHQsIEEsIGxhbWJkYV9qX2FscGhhX2hhbGYsIHRoZXRhKSB7CiAgZGVub20gPC0gbGFtYmRhX2pfYWxwaGFfaGFsZl4yICsgdGhldGFeMgogIG51bWVyYXRvciA8LSBleHAobGFtYmRhX2pfYWxwaGFfaGFsZiAqIHQpICogKGxhbWJkYV9qX2FscGhhX2hhbGYgKiBjb3ModGhldGEgKiB0KSArIHRoZXRhICogc2luKHRoZXRhICogdCkpIC0gbGFtYmRhX2pfYWxwaGFfaGFsZgogIHJldHVybihBICogbnVtZXJhdG9yIC8gZGVub20pCn0KYGBgCgojIyMgRnVuY3Rpb24gYHJldmVyc2Vjb2x1bW5zKClgCgpHaXZlbiBhIG1hdHJpeCBgbWF0YCwgZnVuY3Rpb24gYHJldmVyc2Vjb2x1bW5zKClgIHJldmVyc2VzIHRoZSBvcmRlciBvZiBpdHMgY29sdW1ucy4KCgpgYGB7cn0KcmV2ZXJzZWNvbHVtbnMgPC0gZnVuY3Rpb24obWF0KSB7CiAgcmV0dXJuKG1hdFssIHJldihzZXFfbGVuKG5jb2wobWF0KSkpXSkKfQpgYGAKCgojIyBGb3IgRXVjbGlkZWFuIGRvbWFpbnMgKHJlY3RhbmdsZSkgeyNldWNsaWRlYW5fZG9tYWluc30KCmBgYHtyfQpnZXRzLm1lc2guYW5kLkZFTS5vbi5yZWN0YW5nbGUgPC0gZnVuY3Rpb24oYSwgYiwgbikgewogIG1lc2ggPC0gZm1lc2hlcjo6Zm1fcmNkdF8yZCgKICBsYXR0aWNlID0gZm1lc2hlcjo6Zm1fbGF0dGljZV8yZCgKICB4ID0gc2VxKDAsIGEsIGxlbmd0aC5vdXQgPSBuICsgMSksCiAgeSA9IHNlcSgwLCBiLCBsZW5ndGgub3V0ID0gbiArIDEpCiksIGV4dGVuZCA9IEZBTFNFKQogIEZFTSA8LSBmbWVzaGVyOjpmbV9mZW0obWVzaCwgb3JkZXIgPSAxKQogIHJldHVybihsaXN0KG1lc2ggPSBtZXNoLCAKICAgICAgICAgICAgICBDbCA9IEZFTSRjMCwKICAgICAgICAgICAgICBDID0gRkVNJGMxLAogICAgICAgICAgICAgIEcgPSBGRU0kZzEpKQp9CmBgYAoKCgpgYGB7cn0KY29tcHV0ZV90cnVlX2VpZ2VuX3JlY3RhbmdsZSA8LSBmdW5jdGlvbihhID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2FwcGEgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbV9tYXggPSAzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX21heCA9IDMpIHsKICBsb2NfeCA8LSBsb2NbLDFdCiAgbG9jX3kgPC0gbG9jWywyXQogIE4gPC0gKG1fbWF4KzEpKihuX21heCsxKQogICMgUHJlcGFyZSBsaXN0cwogIEVJR0VOVkFMIDwtIG51bWVyaWMoTikKICBtX3ZlY3RvciA8LSBudW1lcmljKE4pCiAgbl92ZWN0b3IgPC0gbnVtZXJpYyhOKQogIEVJR0VORlVOIDwtIG1hdHJpeCgwLCBucm93ID0gbnJvdyhsb2MpLCBuY29sID0gTikKICAKICBpIDwtIDAgCiAgIyBMb29wIG92ZXIgbW9kZSBpbmRpY2VzCiAgZm9yIChtIGluIDA6bV9tYXgpIHsKICAgIGZvciAobiBpbiAwOm5fbWF4KSB7CiAgICAgIGxhbWJkYV9tbiA8LSBrYXBwYV4yICsgcGleMiAqICgobV4yIC8gYV4yKSArIChuXjIgLyBiXjIpKQogICAgICBFSUdFTlZBTFtpICsgMV0gPC0gbGFtYmRhX21uCiAgICAgICMgRXZhbHVhdGUgZWlnZW5mdW5jdGlvbiBvbiBtZXNoIGdyaWQKICAgICAgcGhpX21uIDwtICBjb3MobSpwaSpsb2NfeC9hKSAqIGNvcyhuKnBpKmxvY195L2IpCiAgICAgIEVJR0VORlVOWywgaSArIDFdIDwtIHBoaV9tbgogICAgICBtX3ZlY3RvcltpICsgMV0gPC0gbQogICAgICBuX3ZlY3RvcltpICsgMV0gPC0gbgogICAgICBpIDwtIGkgKyAxCiAgICB9CiAgfQogICMgU29ydCBlaWdlbnZhbHVlcyBhbmQgY29ycmVzcG9uZGluZyBlaWdlbmZ1bmN0aW9ucwogIGlkeCA8LSBvcmRlcihFSUdFTlZBTCkKICBFSUdFTlZBTCA8LSBFSUdFTlZBTFtpZHhdCiAgRUlHRU5WQUxfQUxQSEEgPC0gRUlHRU5WQUxeKGFscGhhLzIpCiAgRUlHRU5GVU4gPC0gRUlHRU5GVU5bLCBpZHhdCiAgbV92ZWN0b3IgPC0gbV92ZWN0b3JbaWR4XQogIG5fdmVjdG9yIDwtIG5fdmVjdG9yW2lkeF0KICAKICByZXR1cm4obGlzdChFSUdFTlZBTCA9IEVJR0VOVkFMLAogICAgICAgICAgICAgIEVJR0VOVkFMX0FMUEhBID0gRUlHRU5WQUxfQUxQSEEsCiAgICAgICAgICAgICAgRUlHRU5GVU4gPSBFSUdFTkZVTiwKICAgICAgICAgICAgICBtX3ZlY3RvciA9IG1fdmVjdG9yLAogICAgICAgICAgICAgIG5fdmVjdG9yID0gbl92ZWN0b3IpKQp9CmBgYAoKCiMjIEZvciBtYW5pZm9sZHMgKHNwaGVyZSkgeyNtYW5pZm9sZHN9CgpgYGB7cn0KIyBGdW5jdGlvbiB0byBnZXQgZ2xvYmUgZnJvbSBoIHVzaW5nIHNwbGluZQpnbG9iZV9mcm9tX2ggPC0gZnVuY3Rpb24oaCkgewogIHJlYWRlZF9kYXRhIDwtIHJlYWRSRFMoCiAgICBmaWxlID0gaGVyZTo6aGVyZSgiZGF0YV9maWxlcy9oX21pbl9tYXhfdnNfZ2xvYmUuUkRTIikKICApCiAgCiAgZ2xvYmVfdmVjdG9yIDwtIHJlYWRlZF9kYXRhJGdsb2JlX3ZlY3RvcgogIGhfbWluX3ZlY3RvciA8LSByZWFkZWRfZGF0YSRoX21pbl92ZWN0b3IKICAKICBvcmQgPC0gb3JkZXIoaF9taW5fdmVjdG9yKQogIGhfc29ydGVkIDwtIGhfbWluX3ZlY3RvcltvcmRdCiAgZ2xvYmVfc29ydGVkIDwtIGdsb2JlX3ZlY3RvcltvcmRdCiAgCiAgIyBDcmVhdGUgYSBzbW9vdGggc3BsaW5lIGZ1bmN0aW9uOiBnbG9iZSBhcyBmdW5jdGlvbiBvZiBoX21heAogIHNwbGluZV9mdW5jIDwtIHNwbGluZWZ1bih4ID0gaF9zb3J0ZWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gZ2xvYmVfc29ydGVkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gIm1vbm9ILkZDIikKCiAgcmV0dXJuKHJvdW5kKHNwbGluZV9mdW5jKGgpKSkKfQpgYGAKCgpgYGB7cn0KY2FsY3VsYXRlX2xhcGxhY2VfYmVsdHJhbWlfZWlnZW52YWx1ZXMgPC0gZnVuY3Rpb24oa2FwcGEgPSAwLCBMX21heCA9IDUsIHJvdC5pbnYgPSBGQUxTRSkgewogIAogIGVpZ2VudmFsdWVzIDwtIG51bWVyaWMoMCkKICAKICBmb3IgKGwgaW4gMDpMX21heCkgewogICAgbGFtYmRhX2wgPC0ga2FwcGFeMiArIGwgKiAobCArIDEpCiAgICAKICAgIGlmIChyb3QuaW52KSB7CiAgICAgICMgT25seSBvbmUgZWlnZW52YWx1ZSBwZXIgbCAocm90YXRpb25hbCBpbnZhcmlhbmNlKQogICAgICBlaWdlbnZhbHVlcyA8LSBjKGVpZ2VudmFsdWVzLCBsYW1iZGFfbCkKICAgIH0gZWxzZSB7CiAgICAgICMgRnVsbCBtdWx0aXBsaWNpdHk6IChtID0gLWwsLi4uLGwpCiAgICAgIG11bHRpcGxpY2l0eSA8LSAyICogbCArIDEKICAgICAgZWlnZW52YWx1ZXMgPC0gYyhlaWdlbnZhbHVlcywgcmVwKGxhbWJkYV9sLCBtdWx0aXBsaWNpdHkpKQogICAgfQogIH0KICAKICByZXR1cm4oZWlnZW52YWx1ZXMpCn0KCgpjb21wdXRlX3RydWVfZWlnZW5fc3BoZXJlIDwtIGZ1bmN0aW9uKG1lc2gsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGthcHBhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIExfbWF4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdC5pbnYpewogIEVJR0VORlVOIDwtIGZtZXNoZXI6OmZtX3Jhd19iYXNpcygKICAgIG1lc2ggPSBtZXNoLCAKICAgIHR5cGUgPSAic3BoLmhhcm0iLAogICAgbiA9IExfbWF4LCAKICAgIHJvdC5pbnYgPSByb3QuaW52KQogIEVJR0VOVkFMIDwtIGNhbGN1bGF0ZV9sYXBsYWNlX2JlbHRyYW1pX2VpZ2VudmFsdWVzKAogICAga2FwcGEgPSBrYXBwYSwgCiAgICBMX21heCA9IExfbWF4LCAKICAgIHJvdC5pbnYgPSByb3QuaW52KQogIGlkeCA8LSBvcmRlcihFSUdFTlZBTCkKICBFSUdFTlZBTCA8LSBFSUdFTlZBTFtpZHhdCiAgRUlHRU5WQUxfQUxQSEEgPC0gRUlHRU5WQUxeKGFscGhhLzIpCiAgRUlHRU5GVU4gPC0gRUlHRU5GVU5bLCBpZHhdCiAgcmV0dXJuKGxpc3QoRUlHRU5WQUwgPSBFSUdFTlZBTCwKICAgICAgICAgICAgICBFSUdFTlZBTF9BTFBIQSA9IEVJR0VOVkFMX0FMUEhBLAogICAgICAgICAgICAgIEVJR0VORlVOID0gRUlHRU5GVU4pKQp9CmBgYAoKCiMjIFBsb3R0aW5nIGZ1bmN0aW9ucyB7I3Bsb3R0aW5nX2Z1bmN0aW9uc30KCiMjIyBGdW5jdGlvbiBgcGxvdHRpbmcub3JkZXIoKWAKCkdpdmVuIGEgdmVjdG9yIGB2YCBhbmQgYSBncmFwaCBvYmplY3QgYGdyYXBoYCwgZnVuY3Rpb24gYHBsb3R0aW5nLm9yZGVyKClgIG9yZGVycyB0aGUgbWVzaCB2YWx1ZXMgZm9yIHBsb3R0aW5nLgoKYGBge3J9CiMgRnVuY3Rpb24gdG8gb3JkZXIgdGhlIHZlcnRpY2VzIGZvciBwbG90dGluZwpwbG90dGluZy5vcmRlciA8LSBmdW5jdGlvbih2LCBncmFwaCl7CiAgZWRnZV9udW1iZXIgPC0gZ3JhcGgkbWVzaCRWdEVbLCAxXQogIHBvcyA8LSBzdW0oZWRnZV9udW1iZXIgPT0gMSkrMQogIHJldHVybihjKHZbMV0sIHZbMzpwb3NdLCB2WzJdLCB2Wyhwb3MrMSk6bGVuZ3RoKHYpXSwgdlsyXSkpCn0KYGBgCgojIyMgRnVuY3Rpb24gYGdsb2JhbC5zY2VuZS5zZXR0ZXIoKWAKCkdpdmVuIHJhbmdlcyBmb3IgdGhlIGB4YCwgYHlgLCBhbmQgYHpgIGF4ZXMsIGFuZCBhbiBvcHRpb25hbCBhc3BlY3QgcmF0aW8gZm9yIHRoZSBgemAgYXhpcywgZnVuY3Rpb24gYGdsb2JhbC5zY2VuZS5zZXR0ZXIoKWAgc2V0cyB0aGUgc2NlbmUgZm9yIDNEIHBsb3RzIHNvIHRoYXQgYWxsIHBsb3RzIGhhdmUgdGhlIHNhbWUgYXNwZWN0IHJhdGlvIGFuZCBjYW1lcmEgcG9zaXRpb24uCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBzZXQgdGhlIHNjZW5lIGZvciAzRCBwbG90cwpnbG9iYWwuc2NlbmUuc2V0dGVyIDwtIGZ1bmN0aW9uKHhfcmFuZ2UsIHlfcmFuZ2UsIHpfcmFuZ2UsIHpfYXNwZWN0cmF0aW8gPSA0KSB7CiAgCiAgcmV0dXJuKGxpc3QoeGF4aXMgPSBsaXN0KHRpdGxlID0gIngiLCByYW5nZSA9IHhfcmFuZ2UpLAogICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJ5IiwgcmFuZ2UgPSB5X3JhbmdlKSwKICAgICAgICAgICAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAieiIsIHJhbmdlID0gel9yYW5nZSksCiAgICAgICAgICAgICAgYXNwZWN0cmF0aW8gPSBsaXN0KHggPSAyKigxKzIvcGkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IDIqKDIvcGkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeiA9IHpfYXNwZWN0cmF0aW8qKDIvcGkpKSwKICAgICAgICAgICAgICBjYW1lcmEgPSBsaXN0KGV5ZSA9IGxpc3QoeCA9ICgxKzIvcGkpLzIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gNCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHogPSAyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRlciA9IGxpc3QoeCA9ICgxKzIvcGkpLzIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHogPSAwKSkpKQp9CmBgYAoKCmBgYHtyfQpnbG9iYWwuc2NlbmUuc2V0dGVyLnJlYy5hbmQuc3BoZXJlIDwtIGZ1bmN0aW9uKHhfcmFuZ2UsIHlfcmFuZ2UsIHpfcmFuZ2UpIHsKICAKICByZXR1cm4obGlzdCh4YXhpcyA9IGxpc3QodGl0bGUgPSAieCIsIHJhbmdlID0geF9yYW5nZSksCiAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gInkiLCByYW5nZSA9IHlfcmFuZ2UpLAogICAgICAgICAgICAgIHpheGlzID0gbGlzdCh0aXRsZSA9ICJ6IiwgcmFuZ2UgPSB6X3JhbmdlKSwKICAgICAgICAgICAgICBhc3BlY3RyYXRpbyA9IGxpc3QoeCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHogPSAxKSwKICAgICAgICAgICAgICBjYW1lcmEgPSBsaXN0KGV5ZSA9IGxpc3QoeCA9IDIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHogPSAyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRlciA9IGxpc3QoeCA9IDAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHogPSAwKSkpKQp9CgoKZ2xvYmFsLnNjZW5lLnNldHRlci5yZWMuYW5kLnNwaGVyZS5mb3IuaGF0IDwtIGZ1bmN0aW9uKHhfcmFuZ2UsIHlfcmFuZ2UsIHpfcmFuZ2UpIHsKICAKICByZXR1cm4obGlzdCh4YXhpcyA9IGxpc3QodGl0bGUgPSAieCIsIHJhbmdlID0geF9yYW5nZSksCiAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gInkiLCByYW5nZSA9IHlfcmFuZ2UpLAogICAgICAgICAgICAgIHpheGlzID0gbGlzdCh0aXRsZSA9ICJ6IiwgcmFuZ2UgPSB6X3JhbmdlKSwKICAgICAgICAgICAgICBhc3BlY3RyYXRpbyA9IGxpc3QoeCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHogPSAwLjQpLAogICAgICAgICAgICAgIGNhbWVyYSA9IGxpc3QoZXllID0gbGlzdCh4ID0gMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeiA9IDIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VudGVyID0gbGlzdCh4ID0gMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeiA9IDApKSkpCn0KYGBgCgojIyMgRnVuY3Rpb24gYGdyYXBoLnBsb3R0ZXIuM2QoKWAKCkdpdmVuIGEgZ3JhcGggb2JqZWN0IGBncmFwaGAsIGEgc2VxdWVuY2Ugb2YgdGltZSBwb2ludHMgYHRpbWVfc2VxYCwgYW5kIG9uZSBvciBtb3JlIG1hdHJpY2VzIGAuLi5gIHJlcHJlc2VudGluZyBmdW5jdGlvbiB2YWx1ZXMgZGVmaW5lZCBvbiB0aGUgbWVzaCBvZiBgZ3JhcGhgIGF0IGVhY2ggdGltZSBpbiBgdGltZV9zZXFgLCB0aGUgYGdyYXBoLnBsb3R0ZXIuM2QoKWAgZnVuY3Rpb24gZ2VuZXJhdGVzIGFuIGludGVyYWN0aXZlIDNEIHZpc3VhbGl6YXRpb24gb2YgdGhlc2UgdmFsdWVzIG92ZXIgdGltZS4KCmBgYHtyfQojIEZ1bmN0aW9uIHRvIHBsb3QgaW4gM0QKZ3JhcGgucGxvdHRlci4zZC5vbGQgPC0gZnVuY3Rpb24oZ3JhcGgsIHRpbWVfc2VxLCBmcmFtZV92YWxfdG9fZGlzcGxheSwgLi4uKSB7CiAgVV9saXN0IDwtIGxpc3QoLi4uKQogIFVfbmFtZXMgPC0gc2FwcGx5KHN1YnN0aXR1dGUobGlzdCguLi4pKVstMV0sIGRlcGFyc2UpCgogICMgU3BhdGlhbCBjb29yZGluYXRlcwogIHggPC0gcGxvdHRpbmcub3JkZXIoZ3JhcGgkbWVzaCRWWywgMV0sIGdyYXBoKQogIHkgPC0gcGxvdHRpbmcub3JkZXIoZ3JhcGgkbWVzaCRWWywgMl0sIGdyYXBoKQogIHdlaWdodHMgPC0gZ3JhcGgkbWVzaCR3ZWlnaHRzCgogICMgQXBwbHkgcGxvdHRpbmcub3JkZXIgdG8gZWFjaCBVCiAgVV9saXN0IDwtIGxhcHBseShVX2xpc3QsIGZ1bmN0aW9uKFUpIGFwcGx5KFUsIDIsIHBsb3R0aW5nLm9yZGVyLCBncmFwaCA9IGdyYXBoKSkKICBuX3ZhcnMgPC0gbGVuZ3RoKFVfbGlzdCkKCiAgIyBDcmVhdGUgcGxvdF9kYXRhIGZyYW1lIHdpdGggdGltZSBhbmQgcG9zaXRpb24gcmVwbGljYXRlZAogIG5fdGltZSA8LSBuY29sKFVfbGlzdFtbMV1dKQogIGJhc2VfZGF0YSA8LSBkYXRhLmZyYW1lKAogICAgeCA9IHJlcCh4LCB0aW1lcyA9IG5fdGltZSksCiAgICB5ID0gcmVwKHksIHRpbWVzID0gbl90aW1lKSwKICAgIHRoZV9ncmFwaCA9IDAsCiAgICBmcmFtZSA9IHJlcCh0aW1lX3NlcSwgZWFjaCA9IGxlbmd0aCh4KSkKICApCgogICMgQWRkIFUgY29sdW1ucyB0byBwbG90X2RhdGEKICBmb3IgKGkgaW4gc2VxX2Fsb25nKFVfbGlzdCkpIHsKICAgIGJhc2VfZGF0YVtbcGFzdGUwKCJ1IiwgaSldXSA8LSBhcy52ZWN0b3IoVV9saXN0W1tpXV0pCiAgfQoKICBwbG90X2RhdGEgPC0gYmFzZV9kYXRhCgogICMgR2VuZXJhdGUgdmVydGljYWwgbGluZXMKICB2ZXJ0aWNhbF9saW5lc19saXN0IDwtIGxhcHBseShzZXFfYWxvbmcoVV9saXN0KSwgZnVuY3Rpb24oaSkgewogICAgZG8uY2FsbChyYmluZCwgbGFwcGx5KHRpbWVfc2VxLCBmdW5jdGlvbih0KSB7CiAgICAgIGlkeCA8LSB3aGljaChwbG90X2RhdGEkZnJhbWUgPT0gdCkKICAgICAgel92YWxzIDwtIHBsb3RfZGF0YVtbcGFzdGUwKCJ1IiwgaSldXVtpZHhdCiAgICAgIGRhdGEuZnJhbWUoCiAgICAgICAgeCA9IHJlcChwbG90X2RhdGEkeFtpZHhdLCBlYWNoID0gMyksCiAgICAgICAgeSA9IHJlcChwbG90X2RhdGEkeVtpZHhdLCBlYWNoID0gMyksCiAgICAgICAgeiA9IGFzLnZlY3Rvcih0KGNiaW5kKDAsIHpfdmFscywgTkEpKSksCiAgICAgICAgZnJhbWUgPSByZXAodCwgZWFjaCA9IGxlbmd0aChpZHgpICogMykKICAgICAgKQogICAgfSkpCiAgfSkKCiAgIyBTZXQgYXhpcyByYW5nZXMKICB6X3JhbmdlIDwtIHJhbmdlKHVubGlzdChVX2xpc3QpKQogIHhfcmFuZ2UgPC0gcmFuZ2UoeCkKICB5X3JhbmdlIDwtIHJhbmdlKHkpCgogICMgQ3JlYXRlIHBsb3QKICBwIDwtIHBsb3RfbHkocGxvdF9kYXRhLCBmcmFtZSA9IH5mcmFtZSkgJT4lCiAgICBhZGRfdHJhY2UoeCA9IH54LCB5ID0gfnksIHogPSB+dGhlX2dyYXBoLCB0eXBlID0gInNjYXR0ZXIzZCIsIG1vZGUgPSAibGluZXMiLAogICAgICAgICAgICAgIG5hbWUgPSAiIiwgc2hvd2xlZ2VuZCA9IEZBTFNFLAogICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsYWNrIiwgd2lkdGggPSAzKSkKCiAgIyBBZGQgdHJhY2VzIGZvciBlYWNoIHZhcmlhYmxlCiAgY29sb3JzIDwtIHJldih2aXJpZGlzTGl0ZTo6dmlyaWRpcyhuX3ZhcnMpKSAjUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKG1pbihuX3ZhcnMsIDgpLCAiU2V0MSIpCiAgZm9yIChpIGluIHNlcV9hbG9uZyhVX2xpc3QpKSB7CiAgICBwIDwtIGFkZF90cmFjZShwLAogICAgICB4ID0gfngsIHkgPSB+eSwgeiA9IGFzLmZvcm11bGEocGFzdGUwKCJ+dSIsIGkpKSwKICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwgbmFtZSA9IFVfbmFtZXNbaV0sCiAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gY29sb3JzW2ldLCB3aWR0aCA9IDMpKQogIH0KCiAgIyBBZGQgdmVydGljYWwgbGluZXMKICBmb3IgKGkgaW4gc2VxX2Fsb25nKHZlcnRpY2FsX2xpbmVzX2xpc3QpKSB7CiAgICBwIDwtIGFkZF90cmFjZShwLAogICAgICBkYXRhID0gdmVydGljYWxfbGluZXNfbGlzdFtbaV1dLAogICAgICB4ID0gfngsIHkgPSB+eSwgeiA9IH56LCBmcmFtZSA9IH5mcmFtZSwKICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiZ3JheSIsIHdpZHRoID0gMC41KSwKICAgICAgbmFtZSA9ICJWZXJ0aWNhbCBsaW5lcyIsCiAgICAgIHNob3dsZWdlbmQgPSBGQUxTRSkKICB9CiAgZnJhbWVfbmFtZSA8LSBkZXBhcnNlKHN1YnN0aXR1dGUoZnJhbWVfdmFsX3RvX2Rpc3BsYXkpKQogICMgTGF5b3V0IGFuZCBhbmltYXRpb24gY29udHJvbHMKICBwIDwtIHAgJT4lCiAgICBsYXlvdXQoCiAgICAgIHNjZW5lID0gZ2xvYmFsLnNjZW5lLnNldHRlcih4X3JhbmdlLCB5X3JhbmdlLCB6X3JhbmdlKSwKICAgICAgdXBkYXRlbWVudXMgPSBsaXN0KGxpc3QodHlwZSA9ICJidXR0b25zIiwgc2hvd2FjdGl2ZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidXR0b25zID0gbGlzdCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KGxhYmVsID0gIlBsYXkiLCBtZXRob2QgPSAiYW5pbWF0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmdzID0gbGlzdChOVUxMLCBsaXN0KGZyYW1lID0gbGlzdChkdXJhdGlvbiA9IDIwMDAgLyBsZW5ndGgodGltZV9zZXEpLCByZWRyYXcgPSBUUlVFKSwgZnJvbWN1cnJlbnQgPSBUUlVFKSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QobGFiZWwgPSAiUGF1c2UiLCBtZXRob2QgPSAiYW5pbWF0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmdzID0gbGlzdChOVUxMLCBsaXN0KG1vZGUgPSAiaW1tZWRpYXRlIiwgZnJhbWUgPSBsaXN0KGR1cmF0aW9uID0gMCksIHJlZHJhdyA9IEZBTFNFKSkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgKSksCiAgICAgIHRpdGxlID0gcGFzdGUwKGZyYW1lX25hbWUsIjogIiwgZm9ybWF0QyhmcmFtZV92YWxfdG9fZGlzcGxheVsxXSwgZm9ybWF0ID0gImYiLCBkaWdpdHMgPSA0KSkKICAgICkgJT4lCiAgICBwbG90bHlfYnVpbGQoKQoKICBmb3IgKGkgaW4gc2VxX2Fsb25nKHAkeCRmcmFtZXMpKSB7CiAgICBwJHgkZnJhbWVzW1tpXV0kbGF5b3V0IDwtIGxpc3QodGl0bGUgPSBwYXN0ZTAoZnJhbWVfbmFtZSwiOiAiLCBmb3JtYXRDKGZyYW1lX3ZhbF90b19kaXNwbGF5W2ldLCBmb3JtYXQgPSAiZiIsIGRpZ2l0cyA9IDQpKSkKICB9CgogIHJldHVybihwKQp9CmBgYAoKCiMjIyBGdW5jdGlvbiBgZ3JhcGgucGxvdHRlci4zZCgpYAoKR2l2ZW4gYSBncmFwaCBvYmplY3QgYGdyYXBoYCwgYSBzZXF1ZW5jZSBvZiB0aW1lIHBvaW50cyBgdGltZV9zZXFgLCBhIHZlY3RvciBgZnJhbWVfdmFsX3RvX2Rpc3BsYXlgIHdpdGggdmFsdWVzIHRvIGRpc3BsYXkgYXQgZWFjaCBmcmFtZSwgYW5kIGEgbGlzdCBgVV9saXN0YCByZXByZXNlbnRpbmcgZnVuY3Rpb24gdmFsdWVzIGRlZmluZWQgb24gdGhlIG1lc2ggb2YgYGdyYXBoYCBhdCBlYWNoIHRpbWUgaW4gYHRpbWVfc2VxYCwgdGhlIGBncmFwaC5wbG90dGVyLjNkKClgIGZ1bmN0aW9uIGdlbmVyYXRlcyBhbiBpbnRlcmFjdGl2ZSAzRCB2aXN1YWxpemF0aW9uIG9mIHRoZXNlIGZ1bmN0aW9ucyBvdmVyIHRpbWUuCgpgYGB7cn0KZ3JhcGgucGxvdHRlci4zZCA8LSBmdW5jdGlvbihncmFwaCwgdGltZV9zZXEsIGZyYW1lX3ZhbF90b19kaXNwbGF5LCBVX2xpc3QpIHsKICBVX25hbWVzIDwtIG5hbWVzKFVfbGlzdCkgCiAgIyBTcGF0aWFsIGNvb3JkaW5hdGVzCiAgeCA8LSBwbG90dGluZy5vcmRlcihncmFwaCRtZXNoJFZbLCAxXSwgZ3JhcGgpCiAgeSA8LSBwbG90dGluZy5vcmRlcihncmFwaCRtZXNoJFZbLCAyXSwgZ3JhcGgpCiAgd2VpZ2h0cyA8LSBncmFwaCRtZXNoJHdlaWdodHMKCiAgIyBBcHBseSBwbG90dGluZy5vcmRlciB0byBlYWNoIFUKICBVX2xpc3QgPC0gbGFwcGx5KFVfbGlzdCwgZnVuY3Rpb24oVSkgYXBwbHkoVSwgMiwgcGxvdHRpbmcub3JkZXIsIGdyYXBoID0gZ3JhcGgpKQogIG5fdmFycyA8LSBsZW5ndGgoVV9saXN0KQogIAogICMgQ3JlYXRlIHBsb3RfZGF0YSBmcmFtZSB3aXRoIHRpbWUgYW5kIHBvc2l0aW9uIHJlcGxpY2F0ZWQKICBuX3RpbWUgPC0gbmNvbChVX2xpc3RbWzFdXSkKICBiYXNlX2RhdGEgPC0gZGF0YS5mcmFtZSgKICAgIHggPSByZXAoeCwgdGltZXMgPSBuX3RpbWUpLAogICAgeSA9IHJlcCh5LCB0aW1lcyA9IG5fdGltZSksCiAgICB0aGVfZ3JhcGggPSAwLAogICAgZnJhbWUgPSByZXAodGltZV9zZXEsIGVhY2ggPSBsZW5ndGgoeCkpCiAgKQoKICAjIEFkZCBVIGNvbHVtbnMgdG8gcGxvdF9kYXRhCiAgZm9yIChpIGluIHNlcV9hbG9uZyhVX2xpc3QpKSB7CiAgICBiYXNlX2RhdGFbW3Bhc3RlMCgidSIsIGkpXV0gPC0gYXMudmVjdG9yKFVfbGlzdFtbaV1dKQogIH0KCiAgcGxvdF9kYXRhIDwtIGJhc2VfZGF0YQoKICAjIEdlbmVyYXRlIHZlcnRpY2FsIGxpbmVzCiAgdmVydGljYWxfbGluZXNfbGlzdCA8LSBsYXBwbHkoc2VxX2Fsb25nKFVfbGlzdCksIGZ1bmN0aW9uKGkpIHsKICAgIGRvLmNhbGwocmJpbmQsIGxhcHBseSh0aW1lX3NlcSwgZnVuY3Rpb24odCkgewogICAgICBpZHggPC0gd2hpY2gocGxvdF9kYXRhJGZyYW1lID09IHQpCiAgICAgIHpfdmFscyA8LSBwbG90X2RhdGFbW3Bhc3RlMCgidSIsIGkpXV1baWR4XQogICAgICBkYXRhLmZyYW1lKAogICAgICAgIHggPSByZXAocGxvdF9kYXRhJHhbaWR4XSwgZWFjaCA9IDMpLAogICAgICAgIHkgPSByZXAocGxvdF9kYXRhJHlbaWR4XSwgZWFjaCA9IDMpLAogICAgICAgIHogPSBhcy52ZWN0b3IodChjYmluZCgwLCB6X3ZhbHMsIE5BKSkpLAogICAgICAgIGZyYW1lID0gcmVwKHQsIGVhY2ggPSBsZW5ndGgoaWR4KSAqIDMpCiAgICAgICkKICAgIH0pKQogIH0pCgogICMgU2V0IGF4aXMgcmFuZ2VzCiAgel9yYW5nZSA8LSByYW5nZSh1bmxpc3QoVV9saXN0KSkKICB4X3JhbmdlIDwtIHJhbmdlKHgpCiAgeV9yYW5nZSA8LSByYW5nZSh5KQoKICAjIENyZWF0ZSBwbG90CiAgcCA8LSBwbG90X2x5KHBsb3RfZGF0YSwgZnJhbWUgPSB+ZnJhbWUpICU+JQogICAgYWRkX3RyYWNlKHggPSB+eCwgeSA9IH55LCB6ID0gfnRoZV9ncmFwaCwgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgICAgICAgICBuYW1lID0gIiIsIHNob3dsZWdlbmQgPSBGQUxTRSwKICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibGFjayIsIHdpZHRoID0gMykpCgogIGlmIChuX3ZhcnMgPT0gMikgewogICAgY29sb3JzIDwtIFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbChtaW4obl92YXJzLCA4KSwgIlNldDEiKSAKICAgIH0gZWxzZSB7CiAgICBjb2xvcnMgPC0gcmV2KHZpcmlkaXNMaXRlOjp2aXJpZGlzKG5fdmFycykpIAogIH0KICAjIFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbChtaW4obl92YXJzLCA4KSwgIlNldDEiKQogIGZvciAoaSBpbiBzZXFfYWxvbmcoVV9saXN0KSkgewogICAgcCA8LSBhZGRfdHJhY2UocCwKICAgICAgeCA9IH54LCB5ID0gfnksIHogPSBhcy5mb3JtdWxhKHBhc3RlMCgifnUiLCBpKSksCiAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsIG5hbWUgPSBVX25hbWVzW2ldLAogICAgICBsaW5lID0gbGlzdChjb2xvciA9IGNvbG9yc1tpXSwgd2lkdGggPSAzKSkKICB9CgogICMgQWRkIHZlcnRpY2FsIGxpbmVzCiAgZm9yIChpIGluIHNlcV9hbG9uZyh2ZXJ0aWNhbF9saW5lc19saXN0KSkgewogICAgcCA8LSBhZGRfdHJhY2UocCwKICAgICAgZGF0YSA9IHZlcnRpY2FsX2xpbmVzX2xpc3RbW2ldXSwKICAgICAgeCA9IH54LCB5ID0gfnksIHogPSB+eiwgZnJhbWUgPSB+ZnJhbWUsCiAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImdyYXkiLCB3aWR0aCA9IDAuNSksCiAgICAgIG5hbWUgPSAiVmVydGljYWwgbGluZXMiLAogICAgICBzaG93bGVnZW5kID0gRkFMU0UpCiAgfQogIGZyYW1lX25hbWUgPC0gZGVwYXJzZShzdWJzdGl0dXRlKGZyYW1lX3ZhbF90b19kaXNwbGF5KSkKICAjIExheW91dCBhbmQgYW5pbWF0aW9uIGNvbnRyb2xzCiAgcCA8LSBwICU+JQogICAgbGF5b3V0KAogICAgICBzY2VuZSA9IGdsb2JhbC5zY2VuZS5zZXR0ZXIoeF9yYW5nZSwgeV9yYW5nZSwgel9yYW5nZSksCiAgICAgIHVwZGF0ZW1lbnVzID0gbGlzdChsaXN0KHR5cGUgPSAiYnV0dG9ucyIsIHNob3dhY3RpdmUgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnV0dG9ucyA9IGxpc3QoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdChsYWJlbCA9ICJQbGF5IiwgbWV0aG9kID0gImFuaW1hdGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXJncyA9IGxpc3QoTlVMTCwgbGlzdChmcmFtZSA9IGxpc3QoZHVyYXRpb24gPSAyMDAwIC8gbGVuZ3RoKHRpbWVfc2VxKSwgcmVkcmF3ID0gVFJVRSksIGZyb21jdXJyZW50ID0gVFJVRSkpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KGxhYmVsID0gIlBhdXNlIiwgbWV0aG9kID0gImFuaW1hdGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXJncyA9IGxpc3QoTlVMTCwgbGlzdChtb2RlID0gImltbWVkaWF0ZSIsIGZyYW1lID0gbGlzdChkdXJhdGlvbiA9IDApLCByZWRyYXcgPSBGQUxTRSkpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICkpLAogICAgICB0aXRsZSA9IHBhc3RlMChmcmFtZV9uYW1lLCI6ICIsIGZvcm1hdEMoZnJhbWVfdmFsX3RvX2Rpc3BsYXlbMV0sIGZvcm1hdCA9ICJmIiwgZGlnaXRzID0gNCkpCiAgICApICU+JQogICAgcGxvdGx5X2J1aWxkKCkKCiAgZm9yIChpIGluIHNlcV9hbG9uZyhwJHgkZnJhbWVzKSkgewogICAgcCR4JGZyYW1lc1tbaV1dJGxheW91dCA8LSBsaXN0KHRpdGxlID0gcGFzdGUwKGZyYW1lX25hbWUsIjogIiwgZm9ybWF0QyhmcmFtZV92YWxfdG9fZGlzcGxheVtpXSwgZm9ybWF0ID0gImYiLCBkaWdpdHMgPSA0KSkpCiAgfQoKICByZXR1cm4ocCkKfQpgYGAKCiMjIyBGdW5jdGlvbiBgZXJyb3IuYXQuZWFjaC50aW1lLnBsb3R0ZXIoKWAKCkdpdmVuIGEgZ3JhcGggb2JqZWN0IGBncmFwaGAsIGEgbWF0cml4IGBVX3RydWVgIG9mIHRydWUgdmFsdWVzLCBhIG1hdHJpeCBgVV9hcHByb3hgIG9mIGFwcHJveGltYXRlZCB2YWx1ZXMsIGEgc2VxdWVuY2Ugb2YgdGltZSBwb2ludHMgYHRpbWVfc2VxYCwgYW5kIGEgdGltZSBzdGVwIGB0aW1lX3N0ZXBgLCBmdW5jdGlvbiBgZXJyb3IuYXQuZWFjaC50aW1lLnBsb3R0ZXIoKWAgY29tcHV0ZXMgdGhlIGVycm9yIGF0IGVhY2ggdGltZSBzdGVwIGFuZCBnZW5lcmF0ZXMgYSBwbG90IHNob3dpbmcgdGhlIGVycm9yIG92ZXIgdGltZS4KCmBgYHtyfQojIEZ1bmN0aW9uIHRvIHBsb3QgdGhlIGVycm9yIGF0IGVhY2ggdGltZSBzdGVwCmVycm9yLmF0LmVhY2gudGltZS5wbG90dGVyIDwtIGZ1bmN0aW9uKGdyYXBoLCBVX3RydWUsIFVfYXBwcm94LCB0aW1lX3NlcSwgdGltZV9zdGVwKSB7CiAgd2VpZ2h0cyA8LSBncmFwaCRtZXNoJHdlaWdodHMKICBlcnJvcl9hdF9lYWNoX3RpbWUgPC0gdCh3ZWlnaHRzKSAlKiUgKFVfdHJ1ZSAtIFVfYXBwcm94KV4yCiAgZXJyb3IgPC0gc3FydChhcy5kb3VibGUodCh3ZWlnaHRzKSAlKiUgKFVfdHJ1ZSAtIFVfYXBwcm94KV4yICUqJSByZXAodGltZV9zdGVwLCBuY29sKFVfdHJ1ZSkpKSkKICBwIDwtIHBsb3RfbHkoKSAlPiUgCiAgYWRkX3RyYWNlKAogIHggPSB+dGltZV9zZXEsIHkgPSB+ZXJyb3JfYXRfZWFjaF90aW1lLCB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLAogIGxpbmUgPSBsaXN0KGNvbG9yID0gJ2JsdWUnLCB3aWR0aCA9IDIpLAogIG1hcmtlciA9IGxpc3QoY29sb3IgPSAnYmx1ZScsIHNpemUgPSA0KSwKICBuYW1lID0gIiIsCiAgc2hvd2xlZ2VuZCA9IFRSVUUKKSAlPiUgCiAgbGF5b3V0KAogIHRpdGxlID0gcGFzdGUwKCJFcnJvciBhdCBFYWNoIFRpbWUgU3RlcCAoVG90YWwgZXJyb3IgPSAiLCBmb3JtYXRDKGVycm9yLCBmb3JtYXQgPSAiZiIsIGRpZ2l0cyA9IDkpLCAiKSIpLAogIHhheGlzID0gbGlzdCh0aXRsZSA9ICJ0IiksCiAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkVycm9yIiksCiAgbGVnZW5kID0gbGlzdCh4ID0gMC4xLCB5ID0gMC45KQopCiAgcmV0dXJuKHApCn0KYGBgCgojIyMgRnVuY3Rpb24gYGdyYXBoLnBsb3R0ZXIuM2QuY29tcGFyZXIoKWAKCkdpdmVuIGEgZ3JhcGggb2JqZWN0IGBncmFwaGAsIG1hdHJpY2VzIGBVX3RydWVgIGFuZCBgVV9hcHByb3hgIHJlcHJlc2VudGluZyB0cnVlIGFuZCBhcHByb3hpbWF0ZWQgdmFsdWVzLCBhbmQgYSBzZXF1ZW5jZSBvZiB0aW1lIHBvaW50cyBgdGltZV9zZXFgLCBmdW5jdGlvbiBgZ3JhcGgucGxvdHRlci4zZC5jb21wYXJlcigpYCBnZW5lcmF0ZXMgYSAzRCBwbG90IGNvbXBhcmluZyB0aGUgdHJ1ZSBhbmQgYXBwcm94aW1hdGVkIHZhbHVlcyBvdmVyIHRpbWUsIHdpdGggY29sb3ItY29kZWQgdHJhY2VzIGZvciBlYWNoIHRpbWUgcG9pbnQuCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBwbG90IHRoZSAzRCBjb21wYXJpc29uIG9mIFVfdHJ1ZSBhbmQgVV9hcHByb3gKZ3JhcGgucGxvdHRlci4zZC5jb21wYXJlciA8LSBmdW5jdGlvbihncmFwaCwgVV90cnVlLCBVX2FwcHJveCwgdGltZV9zZXEpIHsKICB4IDwtIGdyYXBoJG1lc2gkVlssIDFdOyB5IDwtIGdyYXBoJG1lc2gkVlssIDJdCiAgeCA8LSBwbG90dGluZy5vcmRlcih4LCBncmFwaCk7IHkgPC0gcGxvdHRpbmcub3JkZXIoeSwgZ3JhcGgpCgogIFVfdHJ1ZSA8LSBhcHBseShVX3RydWUsIDIsIHBsb3R0aW5nLm9yZGVyLCBncmFwaCA9IGdyYXBoKQogIFVfYXBwcm94IDwtIGFwcGx5KFVfYXBwcm94LCAyLCBwbG90dGluZy5vcmRlciwgZ3JhcGggPSBncmFwaCkKICBuX3RpbWVzIDwtIGxlbmd0aCh0aW1lX3NlcSkKICAKICB4X3JhbmdlIDwtIHJhbmdlKHgpOyB5X3JhbmdlIDwtIHJhbmdlKHkpOyB6X3JhbmdlIDwtIHJhbmdlKGMoVV90cnVlLCBVX2FwcHJveCkpCiAgCiAgIyBOb3JtYWxpemUgdGltZV9zZXEKICB0aW1lX25vcm1hbGl6ZWQgPC0gKHRpbWVfc2VxIC0gbWluKHRpbWVfc2VxKSkgLyAobWF4KHRpbWVfc2VxKSAtIG1pbih0aW1lX3NlcSkpCiAgYmx1ZXMgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJsaWdodGJsdWUiLCAiYmx1ZSIpKShuX3RpbWVzKQogIHJlZHMgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJtaXN0eXJvc2UiLCAicmVkIikpKG5fdGltZXMpCiAgCiAgIyBBY2N1cmF0ZSBjb2xvcnNjYWxlcwogIGNvbG9yc2NhbGVfZ3JlZW5zIDwtIE1hcChmdW5jdGlvbih0LCBjb2wpIGxpc3QodCwgY29sKSwgdGltZV9ub3JtYWxpemVkLCBibHVlcykKICBjb2xvcnNjYWxlX3JlZHMgPC0gTWFwKGZ1bmN0aW9uKHQsIGNvbCkgbGlzdCh0LCBjb2wpLCB0aW1lX25vcm1hbGl6ZWQsIHJlZHMpCiAgCiAgcCA8LSBwbG90X2x5KCkKICAKICAjIFN0YXRpYyBibGFjayBncmFwaCBzdHJ1Y3R1cmUKICBwIDwtIHAgJT4lCiAgICBhZGRfdHJhY2UoeCA9IHgsIHkgPSB5LCB6ID0gcmVwKDAsIGxlbmd0aCh4KSksCiAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibGFjayIsIHdpZHRoID0gNCksCiAgICAgICAgICAgICAgbmFtZSA9ICJHcmFwaCIsIHNob3dsZWdlbmQgPSBGQUxTRSkKICAKICAjIFVfdHJ1ZSB0cmFjZXMgKGdyZWVuKQogIGZvciAoaSBpbiBzZXFfbGVuKG5fdGltZXMpKSB7CiAgICB6IDwtIFVfdHJ1ZVssIGldCiAgICBwIDwtIGFkZF90cmFjZSgKICAgICAgcCwKICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLAogICAgICBtb2RlID0gImxpbmVzIiwKICAgICAgeCA9IHgsIHkgPSB5LCB6ID0geiwKICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSBibHVlc1tpXSwgd2lkdGggPSA0KSwKICAgICAgc2hvd2xlZ2VuZCA9IEZBTFNFLAogICAgICBzY2VuZSA9ICJzY2VuZSIKICAgICkKICB9CiAgCiAgIyBVX2FwcHJveCB0cmFjZXMgKGRhc2hlZCByZWQpCiAgZm9yIChpIGluIHNlcV9sZW4obl90aW1lcykpIHsKICAgIHogPC0gVV9hcHByb3hbLCBpXQogICAgcCA8LSBhZGRfdHJhY2UoCiAgICAgIHAsCiAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwKICAgICAgbW9kZSA9ICJsaW5lcyIsCiAgICAgIHggPSB4LCB5ID0geSwgeiA9IHosCiAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gcmVkc1tpXSwgd2lkdGggPSA0LCBkYXNoID0gImRvdCIpLAogICAgICBzaG93bGVnZW5kID0gRkFMU0UsCiAgICAgIHNjZW5lID0gInNjZW5lIgogICAgKQogIH0KICAKICAjIER1bW15IGdyZWVuIGNvbG9yYmFyIChUcnVlKSDigJMgd2l0aCB0aWNrcwogIHAgPC0gYWRkX3RyYWNlKAogICAgcCwKICAgIHR5cGUgPSAiaGVhdG1hcCIsCiAgICB6ID0gbWF0cml4KHRpbWVfc2VxLCBucm93ID0gMSksCiAgICBzaG93c2NhbGUgPSBUUlVFLAogICAgY29sb3JzY2FsZSA9IGNvbG9yc2NhbGVfZ3JlZW5zLAogICAgY29sb3JiYXIgPSBsaXN0KAogICAgICB0aXRsZSA9IGxpc3QoZm9udCA9IGxpc3Qoc2l6ZSA9IDEyLCBjb2xvciA9ICJibGFjayIpLCB0ZXh0ID0gIlRpbWUiLCBzaWRlID0gInRvcCIpLAogICAgICBsZW4gPSAwLjksCiAgICAgIHRoaWNrbmVzcyA9IDE1LAogICAgICB4ID0gMS4wMiwKICAgICAgeGFuY2hvciA9ICJsZWZ0IiwKICAgICAgeSA9IDAuNSwKICAgICAgeWFuY2hvciA9ICJtaWRkbGUiLAogICAgICB0aWNrdmFscyA9IE5VTEwsICAgIyBoaWRlIHRpY2sgdmFsdWVzCiAgICAgIHRpY2t0ZXh0ID0gTlVMTCwKICAgICAgdGlja3MgPSAiIiAgICAgICAgICMgYWxzbyBoaWRlcyB0aWNrIG1hcmtzCiAgICApLAogICAgeCA9IG1hdHJpeCh0aW1lX3NlcSwgbnJvdyA9IDEpLAogICAgeSA9IG1hdHJpeCgxLCBucm93ID0gMSksCiAgICBob3ZlcmluZm8gPSAic2tpcCIsCiAgICBvcGFjaXR5ID0gMAogICkKCiMgRHVtbXkgcmVkIGNvbG9yYmFyIChBcHByb3gpIOKAkyBubyB0aWNrcwogIHAgPC0gYWRkX3RyYWNlKAogICAgcCwKICAgIHR5cGUgPSAiaGVhdG1hcCIsCiAgICB6ID0gbWF0cml4KHRpbWVfc2VxLCBucm93ID0gMSksCiAgICBzaG93c2NhbGUgPSBUUlVFLAogICAgY29sb3JzY2FsZSA9IGNvbG9yc2NhbGVfcmVkcywKICAgIGNvbG9yYmFyID0gbGlzdCgKICAgICAgdGl0bGUgPSBsaXN0KGZvbnQgPSBsaXN0KHNpemUgPSAxMiwgY29sb3IgPSAiYmxhY2siKSwgdGV4dCA9ICIuIiwgc2lkZSA9ICJ0b3AiKSwKICAgICAgbGVuID0gMC45LAogICAgICB0aGlja25lc3MgPSAxNSwKICAgICAgeCA9IDEuMDUsCiAgICAgIHhhbmNob3IgPSAibGVmdCIsCiAgICAgIHkgPSAwLjUsCiAgICAgIHlhbmNob3IgPSAibWlkZGxlIgogICAgKSwKICAgIHggPSBtYXRyaXgodGltZV9zZXEsIG5yb3cgPSAxKSwKICAgIHkgPSBtYXRyaXgoMSwgbnJvdyA9IDEpLAogICAgaG92ZXJpbmZvID0gInNraXAiLAogICAgb3BhY2l0eSA9IDAKICApCiAgcCA8LSBwICU+JQogICAgYWRkX3RyYWNlKHggPSB4LCB5ID0geSwgeiA9IHJlcCgwLCBsZW5ndGgoeCkpLAogICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmxhY2siLCB3aWR0aCA9IDQpLAogICAgICAgICAgICAgIG5hbWUgPSAiR3JhcGgiLCBzaG93bGVnZW5kID0gRkFMU0UpCiAgcCA8LSBsYXlvdXQocCwKICAgICAgICAgICAgc2NlbmUgPSBnbG9iYWwuc2NlbmUuc2V0dGVyKHhfcmFuZ2UsIHlfcmFuZ2UsIHpfcmFuZ2UpLAogICAgICAgICAgICB4YXhpcyA9IGxpc3QodmlzaWJsZSA9IEZBTFNFKSwKICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHZpc2libGUgPSBGQUxTRSksCiAgICAgICAgICAgIGFubm90YXRpb25zID0gbGlzdCgKICBsaXN0KAogICAgdGV4dCA9ICJFeGFjdCIsCiAgICB4ID0gMS4wNDUsCiAgICB5ID0gMC41LAogICAgeHJlZiA9ICJwYXBlciIsCiAgICB5cmVmID0gInBhcGVyIiwKICAgIHNob3dhcnJvdyA9IEZBTFNFLAogICAgZm9udCA9IGxpc3Qoc2l6ZSA9IDEyLCBjb2xvciA9ICJibGFjayIpLAogICAgdGV4dGFuZ2xlID0gLTkwCiAgKSwKICBsaXN0KAogICAgdGV4dCA9ICJBcHByb3giLAogICAgeCA9IDEuMDc1LAogICAgeSA9IDAuNSwKICAgIHhyZWYgPSAicGFwZXIiLAogICAgeXJlZiA9ICJwYXBlciIsCiAgICBzaG93YXJyb3cgPSBGQUxTRSwKICAgIGZvbnQgPSBsaXN0KHNpemUgPSAxMiwgY29sb3IgPSAiYmxhY2siKSwKICAgIHRleHRhbmdsZSA9IC05MAogICkKKQoKKQoKICAKICByZXR1cm4ocCkKfQpgYGAKCiMjIyBGdW5jdGlvbiBgZ3JhcGgucGxvdHRlci4zZC5zaW5nbGUoKWAKCkdpdmVuIGEgZ3JhcGggb2JqZWN0IGBncmFwaGAsIGEgbWF0cml4IGBVX3RydWVgIHJlcHJlc2VudGluZyB0cnVlIHZhbHVlcywgYW5kIGEgc2VxdWVuY2Ugb2YgdGltZSBwb2ludHMgYHRpbWVfc2VxYCwgZnVuY3Rpb24gYGdyYXBoLnBsb3R0ZXIuM2Quc2luZ2xlKClgIGdlbmVyYXRlcyBhIDNEIHBsb3Qgb2YgdGhlIHRydWUgdmFsdWVzIG92ZXIgdGltZSwgd2l0aCBjb2xvci1jb2RlZCB0cmFjZXMgZm9yIGVhY2ggdGltZSBwb2ludC4KCmBgYHtyfQojIEZ1bmN0aW9uIHRvIHBsb3QgYSBzaW5nbGUgM0QgbGluZSBmb3IgCmdyYXBoLnBsb3R0ZXIuM2Quc2luZ2xlIDwtIGZ1bmN0aW9uKGdyYXBoLCBVX3RydWUsIHRpbWVfc2VxKSB7CiAgeCA8LSBncmFwaCRtZXNoJFZbLCAxXTsgeSA8LSBncmFwaCRtZXNoJFZbLCAyXQogIHggPC0gcGxvdHRpbmcub3JkZXIoeCwgZ3JhcGgpOyB5IDwtIHBsb3R0aW5nLm9yZGVyKHksIGdyYXBoKQoKICBVX3RydWUgPC0gYXBwbHkoVV90cnVlLCAyLCBwbG90dGluZy5vcmRlciwgZ3JhcGggPSBncmFwaCkKICBuX3RpbWVzIDwtIGxlbmd0aCh0aW1lX3NlcSkKICAKICB4X3JhbmdlIDwtIHJhbmdlKHgpOyB5X3JhbmdlIDwtIHJhbmdlKHkpOyB6X3JhbmdlIDwtIHJhbmdlKFVfdHJ1ZSkKICB6X3JhbmdlWzFdIDwtIHpfcmFuZ2VbMV0gLSAxMF4tNgogIHZpcmlkaXNfY29sb3JzIDwtIHZpcmlkaXNMaXRlOjp2aXJpZGlzKDEwMCkKICAKICAjIE5vcm1hbGl6ZSB0aW1lX3NlcQogIHRpbWVfbm9ybWFsaXplZCA8LSAodGltZV9zZXEgLSBtaW4odGltZV9zZXEpKSAvIChtYXgodGltZV9zZXEpIC0gbWluKHRpbWVfc2VxKSkKICAjZ3JlZW5zIDwtIGNvbG9yUmFtcFBhbGV0dGUoYygicGFsZWdyZWVuIiwgImRhcmtncmVlbiIpKShuX3RpbWVzKQogIGdyZWVucyA8LSBjb2xvclJhbXBQYWxldHRlKGModmlyaWRpc19jb2xvcnNbMV0sIHZpcmlkaXNfY29sb3JzWzUwXSwgIHZpcmlkaXNfY29sb3JzWzEwMF0pKShuX3RpbWVzKQogICMgQWNjdXJhdGUgY29sb3JzY2FsZXMKICBjb2xvcnNjYWxlX2dyZWVucyA8LSBNYXAoZnVuY3Rpb24odCwgY29sKSBsaXN0KHQsIGNvbCksIHRpbWVfbm9ybWFsaXplZCwgZ3JlZW5zKQogIAogIHAgPC0gcGxvdF9seSgpCiAgCiAgIyBBZGQgdGhlIDNEIGxpbmVzIHdpdGggZmFkaW5nIGdyZWVuIGNvbG9yCiAgZm9yIChpIGluIHNlcV9sZW4obl90aW1lcykpIHsKICAgIHogPC0gVV90cnVlWywgaV0KICAgIAogICAgcCA8LSBhZGRfdHJhY2UoCiAgICAgIHAsCiAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwKICAgICAgbW9kZSA9ICJsaW5lcyIsCiAgICAgIHggPSB4LAogICAgICB5ID0geSwKICAgICAgeiA9IHosCiAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gZ3JlZW5zW2ldLCB3aWR0aCA9IDIpLAogICAgICBzaG93bGVnZW5kID0gRkFMU0UsCiAgICAgIHNjZW5lID0gInNjZW5lIgogICAgKQogIH0KICBwIDwtIHAgJT4lCiAgICBhZGRfdHJhY2UoeCA9IHgsIHkgPSB5LCB6ID0gcmVwKDAsIGxlbmd0aCh4KSksCiAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibGFjayIsIHdpZHRoID0gNSksCiAgICAgICAgICAgICAgbmFtZSA9ICJHcmFwaCIsIHNob3dsZWdlbmQgPSBGQUxTRSkKICAjIEFkZCBkdW1teSBoZWF0bWFwIHRvIHNob3cgY29sb3JiYXIgKG5vdCBwYXJ0IG9mIHNjZW5lKQogIHAgPC0gYWRkX3RyYWNlKAogICAgcCwKICAgIHR5cGUgPSAiaGVhdG1hcCIsCiAgICB6ID0gbWF0cml4KHRpbWVfc2VxLCBucm93ID0gMSksCiAgICBzaG93c2NhbGUgPSBUUlVFLAogICAgY29sb3JzY2FsZSA9IGNvbG9yc2NhbGVfZ3JlZW5zLAogICAgY29sb3JiYXIgPSBsaXN0KAogICAgdGl0bGUgPSBsaXN0KGZvbnQgPSBsaXN0KHNpemUgPSAxMiwgY29sb3IgPSAiYmxhY2siKSwgdGV4dCA9ICJUaW1lIiwgc2lkZSA9ICJ0b3AiKSwKICAgIGxlbiA9IDAuOSwgICAgICAgICAjIGhlaWdodCAoMCB0byAxKQogICAgdGhpY2tuZXNzID0gMTUsICAgICAjIHdpZHRoIGluIHBpeGVscwogICAgeCA9IDEuMDIsICAgICAgICAgICAjIHNoaWZ0IGl0IHNsaWdodGx5IHJpZ2h0IG9mIHRoZSBwbG90CiAgICB4YW5jaG9yID0gImxlZnQiLAogICAgeSA9IDAuNSwKICAgIHlhbmNob3IgPSAibWlkZGxlIiksCiAgICB4ID0gbWF0cml4KHRpbWVfc2VxLCBucm93ID0gMSksCiAgICB5ID0gbWF0cml4KDEsIG5yb3cgPSAxKSwKICAgIGhvdmVyaW5mbyA9ICJza2lwIiwKICAgIG9wYWNpdHkgPSAwCiAgKQogIAogIHAgPC0gbGF5b3V0KHAsCiAgICAgICAgICAgICAgc2NlbmUgPSBnbG9iYWwuc2NlbmUuc2V0dGVyKHhfcmFuZ2UsIHlfcmFuZ2UsIHpfcmFuZ2UpLAogICAgICAgICAgICAgIHhheGlzID0gbGlzdCh2aXNpYmxlID0gRkFMU0UpLAogICAgICAgICAgICAgIHlheGlzID0gbGlzdCh2aXNpYmxlID0gRkFMU0UpCiAgKQogIAogIHJldHVybihwKQp9CmBgYAoKIyMjIEZ1bmN0aW9uIGBlcnJvci5jb252ZXJnZW5jZS5wbG90dGVyKClgCgpHaXZlbiBhIHZlY3RvciBgeF9heGlzX3ZlY3RvcmAsIGEgdmVjdG9yIGBhbHBoYV92ZWN0b3JgIG9mIHBhcmFtZXRlciB2YWx1ZXMsIGEgbWF0cml4IGBlcnJvcnNgIHdpdGggZXJyb3IgdmFsdWVzLCB2ZWN0b3JzIGB0aGVvcmV0aWNhbF9yYXRlc2AgYW5kIGBvYnNlcnZlZF9yYXRlc2Agd2l0aCBjb252ZXJnZW5jZSByYXRlcywgYSBmdW5jdGlvbiBgbGluZV9lcXVhdGlvbl9mdW5gIChlaXRoZXIgYGxvZ2xvZ19saW5lX2VxdWF0aW9uYCBvciBgZXhwX2xpbmVfZXF1YXRpb25gKSwgYW5kIHBsb3QgdGl0bGVzIGFuZCBsYWJlbHMsIGZ1bmN0aW9uIGBlcnJvci5jb252ZXJnZW5jZS5wbG90dGVyKClgIGdlbmVyYXRlcyBhIGNvbnZlcmdlbmNlIHBsb3Qgc2hvd2luZyBlcnJvcnMgYW5kIGd1aWRpbmcgbGluZXMuCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBwbG90IHRoZSBlcnJvciBjb252ZXJnZW5jZQplcnJvci5jb252ZXJnZW5jZS5wbG90dGVyIDwtIGZ1bmN0aW9uKHhfYXhpc192ZWN0b3IsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhX3ZlY3RvciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXJyb3JzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVvcmV0aWNhbF9yYXRlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2JzZXJ2ZWRfcmF0ZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZV9lcXVhdGlvbl9mdW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlnX3RpdGxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHhfYXhpc19sYWJlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcHBseV9zcXJ0ID0gRkFMU0UpIHsKICAKICB4X3ZlYyA8LSBpZiAoYXBwbHlfc3FydCkgc3FydCh4X2F4aXNfdmVjdG9yKSBlbHNlIHhfYXhpc192ZWN0b3IKICAKICBndWlkaW5nX2xpbmVzIDwtIGNvbXB1dGVfZ3VpZGluZ19saW5lcyh4X2F4aXNfdmVjdG9yID0geF92ZWMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVycm9ycyA9IGVycm9ycywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlb3JldGljYWxfcmF0ZXMgPSB0aGVvcmV0aWNhbF9yYXRlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZV9lcXVhdGlvbl9mdW4gPSBsaW5lX2VxdWF0aW9uX2Z1bikKICAKICBkZWZhdWx0X2NvbG9ycyA8LSBzY2FsZXM6Omh1ZV9wYWwoKShsZW5ndGgoYWxwaGFfdmVjdG9yKSkKICAKICBwbG90X2xpbmVzIDwtIGxhcHBseSgxOm5jb2woZ3VpZGluZ19saW5lcyksIGZ1bmN0aW9uKGkpIHsKICAgIGdlb21fbGluZSgKICAgICAgZGF0YSA9IGRhdGEuZnJhbWUoeCA9IHhfdmVjLCB5ID0gZ3VpZGluZ19saW5lc1ssIGldKSwKICAgICAgYWVzKHggPSB4LCB5ID0geSksCiAgICAgIGNvbG9yID0gZGVmYXVsdF9jb2xvcnNbaV0sCiAgICAgIGxpbmV0eXBlID0gImRhc2hlZCIsCiAgICAgIHNob3cubGVnZW5kID0gRkFMU0UKICAgICkKICB9KQogIAogIGRmIDwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoeF92ZWMsIGVycm9ycykpCiAgY29sbmFtZXMoZGYpIDwtIGMoInhfYXhpc192ZWN0b3IiLCBhbHBoYV92ZWN0b3IpCiAgZGZfbWVsdGVkIDwtIG1lbHQoZGYsIGlkLnZhcnMgPSAieF9heGlzX3ZlY3RvciIsIHZhcmlhYmxlLm5hbWUgPSAiY29sdW1uIiwgdmFsdWUubmFtZSA9ICJ2YWx1ZSIpCiAgCiAgY3VzdG9tX2xhYmVscyA8LSBwYXN0ZTAoZm9ybWF0QyhhbHBoYV92ZWN0b3IsIGZvcm1hdCA9ICJmIiwgZGlnaXRzID0gMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICIgfCAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXRDKHRoZW9yZXRpY2FsX3JhdGVzLCBmb3JtYXQgPSAiZiIsIGRpZ2l0cyA9IDQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAiIHwgIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgZm9ybWF0QyhvYnNlcnZlZF9yYXRlcywgZm9ybWF0ID0gImYiLCBkaWdpdHMgPSA0KSkKICAKICBkZl9tZWx0ZWQkY29sdW1uIDwtIGZhY3RvcihkZl9tZWx0ZWQkY29sdW1uLCBsZXZlbHMgPSBhbHBoYV92ZWN0b3IsIGxhYmVscyA9IGN1c3RvbV9sYWJlbHMpCgogIHAgPC0gZ2dwbG90KCkgKwogICAgZ2VvbV9saW5lKGRhdGEgPSBkZl9tZWx0ZWQsIGFlcyh4ID0geF9heGlzX3ZlY3RvciwgeSA9IHZhbHVlLCBjb2xvciA9IGNvbHVtbikpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGRmX21lbHRlZCwgYWVzKHggPSB4X2F4aXNfdmVjdG9yLCB5ID0gdmFsdWUsIGNvbG9yID0gY29sdW1uKSkgKwogICAgcGxvdF9saW5lcyArCiAgICBsYWJzKAogICAgICB0aXRsZSA9IGZpZ190aXRsZSwKICAgICAgeCA9IHhfYXhpc19sYWJlbCwKICAgICAgeSA9IGV4cHJlc3Npb24oRXJyb3IpLAogICAgICBjb2xvciA9ICIgICAgICAgICAgzrEgIHwgdGhlbyAgfCBvYnMiCiAgICApICsKICAgIChpZiAoYXBwbHlfc3FydCkgewogICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0geF92ZWMsIGxhYmVscyA9IHJvdW5kKHhfYXhpc192ZWN0b3IsIDQpKQogICAgfSBlbHNlIHsKICAgICAgc2NhbGVfeF9sb2cxMChicmVha3MgPSB4X2F4aXNfdmVjdG9yLCBsYWJlbHMgPSByb3VuZCh4X2F4aXNfdmVjdG9yLCA0KSkKICAgIH0pICsKICAgIChpZiAoYXBwbHlfc3FydCkgewogICAgICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9nIiwgbGFiZWxzID0gc2NhbGVzOjpzY2llbnRpZmljX2Zvcm1hdCgpKQogICAgfSBlbHNlIHsKICAgICAgc2NhbGVfeV9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OnNjaWVudGlmaWNfZm9ybWF0KCkpCiAgICB9KSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiUGFsYXRpbm8iKSwKICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgICAgICAgbGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIsCiAgICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigwLCAwLCAwLCAwKSwKICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemUgPSAxOCwgZmFjZSA9ICJib2xkIikpCiAgCiAgcmV0dXJuKHApCn0KCmBgYAoKCiMjIyBGdW5jdGlvbiBgZ3JhcGgucGxvdHRlci4zZC5zdGF0aWMoKWAKCkdpdmVuIGEgZ3JhcGggb2JqZWN0IGBncmFwaGAgYW5kIGEgbGlzdCBgel9saXN0YCBvZiBmdW5jdGlvbiB2YWx1ZXMgZGVmaW5lZCBvbiB0aGUgbWVzaCBvZiBgZ3JhcGhgLCBmdW5jdGlvbiBgZ3JhcGgucGxvdHRlci4zZC5zdGF0aWMoKWAgZ2VuZXJhdGVzIGEgc3RhdGljIDNEIHBsb3Qgb2YgdGhlc2UgdmFsdWVzLgoKYGBge3J9CmdyYXBoLnBsb3R0ZXIuM2Quc3RhdGljIDwtIGZ1bmN0aW9uKGdyYXBoLCB6X2xpc3QpIHsKICB4IDwtIHBsb3R0aW5nLm9yZGVyKGdyYXBoJG1lc2gkVlssIDFdLCBncmFwaCkKICB5IDwtIHBsb3R0aW5nLm9yZGVyKGdyYXBoJG1lc2gkVlssIDJdLCBncmFwaCkKICBVX25hbWVzIDwtIG5hbWVzKHpfbGlzdCkKICBuX3ZhcnMgPC0gbGVuZ3RoKHpfbGlzdCkKICB6X2xpc3QgPC0gbGFwcGx5KHpfbGlzdCwgZnVuY3Rpb24oeikgcGxvdHRpbmcub3JkZXIoeiwgZ3JhcGgpKQoKICAjIEF4aXMgcmFuZ2VzCiAgel9yYW5nZSA8LSByYW5nZSh1bmxpc3Qoel9saXN0KSkKICB4X3JhbmdlIDwtIHJhbmdlKHgpCiAgeV9yYW5nZSA8LSByYW5nZSh5KQoKICBpZiAobl92YXJzID09IDIpIHsKICAgIGNvbG9ycyA8LSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwobWluKG5fdmFycywgOCksICJTZXQxIikgCiAgICB9IGVsc2UgewogICAgY29sb3JzIDwtIHJldih2aXJpZGlzTGl0ZTo6dmlyaWRpcyhuX3ZhcnMpKSAKICB9CiAgcCA8LSBwbG90X2x5KCkKCiAgZm9yIChpIGluIHNlcV9hbG9uZyh6X2xpc3QpKSB7CiAgICB6IDwtIHpfbGlzdFtbaV1dCgogICAgIyBNYWluIDNEIGN1cnZlCiAgICBwIDwtIGFkZF90cmFjZSgKICAgICAgcCwKICAgICAgeCA9IHgsIHkgPSB5LCB6ID0geiwKICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSBjb2xvcnNbaV0sIHdpZHRoID0gMyksCiAgICAgIG5hbWUgPSBVX25hbWVzW2ldLCBzaG93bGVnZW5kID0gVFJVRQogICAgKQoKICAgICMgRWZmaWNpZW50IHZlcnRpY2FsIGxpbmVzOiBvbmUgdHJhY2Ugd2l0aCBicmVha3MgKE5BKQogICAgeF92ZXJ0IDwtIHJlcCh4LCBlYWNoID0gMykKICAgIHlfdmVydCA8LSByZXAoeSwgZWFjaCA9IDMpCiAgICB6X3ZlcnQgPC0gdW5saXN0KGxhcHBseSh6LCBmdW5jdGlvbih6aikgYygwLCB6aiwgTkEpKSkKCiAgICBwIDwtIGFkZF90cmFjZSgKICAgICAgcCwKICAgICAgeCA9IHhfdmVydCwgeSA9IHlfdmVydCwgeiA9IHpfdmVydCwKICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiZ3JheSIsIHdpZHRoID0gMC41KSwKICAgICAgc2hvd2xlZ2VuZCA9IEZBTFNFCiAgICApCiAgfQogIHAgPC0gcCAlPiUgYWRkX3RyYWNlKHggPSB4LCB5ID0geSwgeiA9IHgqMCwgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibGFjayIsIHdpZHRoID0gMyksCiAgICAgICAgICAgICAgbmFtZSA9ICJ0aGVncmFwaCIsIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lCiAgICBsYXlvdXQoc2NlbmUgPSBnbG9iYWwuc2NlbmUuc2V0dGVyKHhfcmFuZ2UsIHlfcmFuZ2UsIHpfcmFuZ2UpKQogIHJldHVybihwKQp9CgpgYGAKCgojIyMgRnVuY3Rpb24gYGdyYXBoLnBsb3R0ZXIuM2QudHdvLm1lc2hlcy50aW1lKClgCgpHaXZlbiB0d28gZ3JhcGggb2JqZWN0cyBgZ3JhcGhfZmluZXJgIGFuZCBgZ3JhcGhfY29hcnNlcmAsIHNlcXVlbmNlcyBvZiB0aW1lIHBvaW50cyBgdGltZV9zZXFgIGFuZCBgZnJhbWVfdmFsX3RvX2Rpc3BsYXlgLCBhbmQgbGlzdHMgYGZzX2ZpbmVyYCBhbmQgYGZzX2NvYXJzZXJgIG9mIGZ1bmN0aW9uIHZhbHVlcyBkZWZpbmVkIG9uIHRoZSBtZXNoZXMgb2YgYGdyYXBoX2ZpbmVyYCBhbmQgYGdyYXBoX2NvYXJzZXJgIHJlc3BlY3RpdmVseSwgZnVuY3Rpb24gYGdyYXBoLnBsb3R0ZXIuM2QudHdvLm1lc2hlcy50aW1lKClgIGdlbmVyYXRlcyBhbiBpbnRlcmFjdGl2ZSAzRCB2aXN1YWxpemF0aW9uIGNvbXBhcmluZyB0aGVzZSBmdW5jdGlvbnMgb3ZlciB0aW1lLgoKCmBgYHtyfQpncmFwaC5wbG90dGVyLjNkLnR3by5tZXNoZXMudGltZSA8LSBmdW5jdGlvbihncmFwaF9maW5lciwgZ3JhcGhfY29hcnNlciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVfc2VxLCBmcmFtZV92YWxfdG9fZGlzcGxheSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnNfZmluZXIgPSBsaXN0KCksIGZzX2NvYXJzZXIgPSBsaXN0KCkpIHsKICAjIFNwYXRpYWwgY29vcmRpbmF0ZXMgKG9yZGVyZWQgZm9yIHBsb3R0aW5nKQogIHhfZmluZXIgPC0gcGxvdHRpbmcub3JkZXIoZ3JhcGhfZmluZXIkbWVzaCRWWywgMV0sIGdyYXBoX2ZpbmVyKQogIHlfZmluZXIgPC0gcGxvdHRpbmcub3JkZXIoZ3JhcGhfZmluZXIkbWVzaCRWWywgMl0sIGdyYXBoX2ZpbmVyKQogIHhfY29hcnNlciA8LSBwbG90dGluZy5vcmRlcihncmFwaF9jb2Fyc2VyJG1lc2gkVlssIDFdLCBncmFwaF9jb2Fyc2VyKQogIHlfY29hcnNlciA8LSBwbG90dGluZy5vcmRlcihncmFwaF9jb2Fyc2VyJG1lc2gkVlssIDJdLCBncmFwaF9jb2Fyc2VyKQogIAogIG5fdGltZSA8LSBpZiAobGVuZ3RoKGZzX2ZpbmVyKSA+IDApIG5jb2woZnNfZmluZXJbWzFdXSkgZWxzZSBuY29sKGZzX2NvYXJzZXJbWzFdXSkKCiAgIyBIZWxwZXI6IG1ha2UgZGF0YWZyYW1lIGZyb20gb25lIGZ1bmN0aW9uCiAgbWFrZV9kZiA8LSBmdW5jdGlvbihmX21hdCwgZ3JhcGgsIHgsIHksIG1lc2hfbmFtZSkgewogICAgeiA8LSBhcHBseShmX21hdCwgMiwgcGxvdHRpbmcub3JkZXIsIGdyYXBoID0gZ3JhcGgpCiAgICBkYXRhLmZyYW1lKAogICAgICB4ID0gcmVwKHgsIHRpbWVzID0gbl90aW1lKSwKICAgICAgeSA9IHJlcCh5LCB0aW1lcyA9IG5fdGltZSksCiAgICAgIHogPSBhcy52ZWN0b3IoeiksCiAgICAgIGZyYW1lID0gcmVwKHRpbWVfc2VxLCBlYWNoID0gbGVuZ3RoKHgpKSwKICAgICAgbWVzaCA9IG1lc2hfbmFtZQogICAgKQogIH0KICAKICAjIEJ1aWxkIGRhdGEgZm9yIGZpbmVyIGZ1bmN0aW9ucwogIGRhdGFfZmluZXJfbGlzdCA8LSBsYXBwbHkobmFtZXMoZnNfZmluZXIpLCBmdW5jdGlvbihubSkgewogICAgbWFrZV9kZihmc19maW5lcltbbm1dXSwgZ3JhcGhfZmluZXIsIHhfZmluZXIsIHlfZmluZXIsIG5tKQogIH0pCiAgCiAgIyBCdWlsZCBkYXRhIGZvciBjb2Fyc2VyIGZ1bmN0aW9ucwogIGRhdGFfY29hcnNlcl9saXN0IDwtIGxhcHBseShuYW1lcyhmc19jb2Fyc2VyKSwgZnVuY3Rpb24obm0pIHsKICAgIG1ha2VfZGYoZnNfY29hcnNlcltbbm1dXSwgZ3JhcGhfY29hcnNlciwgeF9jb2Fyc2VyLCB5X2NvYXJzZXIsIG5tKQogIH0pCiAgCiAgIyBDb21iaW5lCiAgYWxsX2RhdGEgPC0gYyhkYXRhX2ZpbmVyX2xpc3QsIGRhdGFfY29hcnNlcl9saXN0KQogIAogICMgQmFzZWxpbmUgZ3JhcGggKG9uIGZpbmVyIG1lc2ggZm9yIGNvbnNpc3RlbmN5KQogIGRhdGFfZ3JhcGggPC0gZGF0YS5mcmFtZSgKICAgIHggPSByZXAoeF9maW5lciwgdGltZXMgPSBuX3RpbWUpLAogICAgeSA9IHJlcCh5X2ZpbmVyLCB0aW1lcyA9IG5fdGltZSksCiAgICB6ID0gMCwKICAgIGZyYW1lID0gcmVwKHRpbWVfc2VxLCBlYWNoID0gbGVuZ3RoKHhfZmluZXIpKSwKICAgIG1lc2ggPSAiR3JhcGgiCiAgKQogIAojIC0tLS0tLS0tLSBWZXJ0aWNhbCBsaW5lcyBoZWxwZXIgLS0tLS0tLS0tLQp2ZXJ0aWNhbF9saW5lcyA8LSBmdW5jdGlvbih4LCB5LCB6LCBmcmFtZV92YWxzLCBtZXNoX25hbWUpIHsKICBkby5jYWxsKHJiaW5kLCBsYXBwbHkoc2VxX2Fsb25nKGZyYW1lX3ZhbHMpLCBmdW5jdGlvbihpKSB7CiAgICBpZHggPC0gKChpIC0gMSkgKiBsZW5ndGgoeCkgKyAxKTooaSAqIGxlbmd0aCh4KSkKICAgIGRhdGEuZnJhbWUoCiAgICAgIHggPSByZXAoeCwgZWFjaCA9IDMpLAogICAgICB5ID0gcmVwKHksIGVhY2ggPSAzKSwKICAgICAgeiA9IGFzLnZlY3Rvcih0KGNiaW5kKDAsIHpbaWR4XSwgTkEpKSksCiAgICAgIGZyYW1lID0gcmVwKGZyYW1lX3ZhbHNbaV0sIGVhY2ggPSBsZW5ndGgoeCkgKiAzKSwKICAgICAgbWVzaCA9IG1lc2hfbmFtZQogICAgKQogIH0pKQp9CgojIC0tLS0tLS0tLSBDb21wdXRlIHZlcnRpY2FsIGxpbmVzIHBlciBtZXNoIHVzaW5nIG1heCBhYnNvbHV0ZSB2YWx1ZSAtLS0tLS0tLS0KbWFrZV92ZXJ0aWNhbF9mcm9tX2xpc3QgPC0gZnVuY3Rpb24oZGF0YV9saXN0LCB4LCB5LCBtZXNoX25hbWUpIHsKICBpZiAobGVuZ3RoKGRhdGFfbGlzdCkgPT0gMCkgcmV0dXJuKE5VTEwpCiAgCiAgIyBSZXNoYXBlIGVhY2ggZnVuY3Rpb24ncyB6IGJhY2sgdG8gbWF0cml4OiAobm9kZXMgw5cgdGltZSkKICB6X21hdHMgPC0gbGFwcGx5KGRhdGFfbGlzdCwgZnVuY3Rpb24oZGYpIHsKICAgIG1hdHJpeChkZiR6LCBucm93ID0gbGVuZ3RoKHgpLCBuY29sID0gbGVuZ3RoKHRpbWVfc2VxKSkKICB9KQogIAogICMgU3RhY2sgaW50byAzRCBhcnJheTogKG5vZGVzIMOXIHRpbWUgw5cgZnVuY3Rpb25zKQogIGFyciA8LSBhcnJheSh1bmxpc3Qoel9tYXRzKSwgZGltID0gYyhsZW5ndGgoeCksIGxlbmd0aCh0aW1lX3NlcSksIGxlbmd0aCh6X21hdHMpKSkKICAKICAjIEZvciBlYWNoIG5vZGUgw5cgdGltZSwgc2VsZWN0IHRoZSBlbnRyeSB3aXRoIGxhcmdlc3QgYWJzb2x1dGUgdmFsdWUgKGtlZXAgc2lnbikKICBpZHggPC0gYXBwbHkoYXJyLCBjKDEsIDIpLCBmdW5jdGlvbih2KSB3aGljaC5tYXgoYWJzKHYpKSkKICB6X3NpZ25lZF9tYXggPC0gbWFwcGx5KGZ1bmN0aW9uKGksIGopIGFycltpLCBqLCBpZHhbaSwgal1dLAogICAgICAgICAgICAgICAgICAgICAgICAgcmVwKDE6bGVuZ3RoKHgpLCB0aW1lcyA9IGxlbmd0aCh0aW1lX3NlcSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgcmVwKDE6bGVuZ3RoKHRpbWVfc2VxKSwgZWFjaCA9IGxlbmd0aCh4KSkpCiAgCiAgIyBGbGF0dGVuIGJhY2sgaW50byBsb25nIHZlY3RvcgogIHpfc2lnbmVkX21heCA8LSBhcy52ZWN0b3Ioel9zaWduZWRfbWF4KQogIAogIHZlcnRpY2FsX2xpbmVzKHgsIHksIHpfc2lnbmVkX21heCwgdGltZV9zZXEsIG1lc2hfbmFtZSkKfQoKCnZlcnRpY2FsX2ZpbmVyICAgPC0gbWFrZV92ZXJ0aWNhbF9mcm9tX2xpc3QoZGF0YV9maW5lcl9saXN0LCAgIHhfZmluZXIsICAgeV9maW5lciwgICAiZmluZXIiKQp2ZXJ0aWNhbF9jb2Fyc2VyIDwtIG1ha2VfdmVydGljYWxfZnJvbV9saXN0KGRhdGFfY29hcnNlcl9saXN0LCB4X2NvYXJzZXIsIHlfY29hcnNlciwgImNvYXJzZXIiKQoKICAKICAjIENvbXB1dGUgcmFuZ2VzCiAgYWxsX3ogPC0gdW5saXN0KGxhcHBseShhbGxfZGF0YSwgZnVuY3Rpb24oZGYpIGRmJHopKQogIHhfcmFuZ2UgPC0gcmFuZ2UoYyh4X2ZpbmVyLCB4X2NvYXJzZXIpKQogIHlfcmFuZ2UgPC0gcmFuZ2UoYyh5X2ZpbmVyLCB5X2NvYXJzZXIpKQogIHpfcmFuZ2UgPC0gcmFuZ2UoYWxsX3opCiAgCiAgIyAtLS0tLS0tLS0gUGxvdGx5IG9iamVjdCAtLS0tLS0tLS0tCiAgcCA8LSBwbG90X2x5KGZyYW1lID0gfmZyYW1lKQogIAogICMgQWRkIHRyYWNlcyBmb3IgZmluZXIgKyBjb2Fyc2VyIChsb29waW5nIGF1dG9tYXRpY2FsbHkgd2l0aCBuYW1lcykKICBmb3IgKGRmIGluIGFsbF9kYXRhKSB7CiAgICBwIDwtIHAgJT4lCiAgICAgIGFkZF90cmFjZShkYXRhID0gZGYsCiAgICAgICAgICAgICAgICB4ID0gfngsIHkgPSB+eSwgeiA9IH56LAogICAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KHdpZHRoID0gMyksCiAgICAgICAgICAgICAgICBuYW1lID0gdW5pcXVlKGRmJG1lc2gpKQogIH0KICAKICAjIEFkZCBiYXNlbGluZQogIHAgPC0gcCAlPiUKICAgIGFkZF90cmFjZShkYXRhID0gZGF0YV9ncmFwaCwKICAgICAgICAgICAgICB4ID0gfngsIHkgPSB+eSwgeiA9IH56LAogICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmxhY2siLCB3aWR0aCA9IDIpLAogICAgICAgICAgICAgIG5hbWUgPSAiR3JhcGgiLCBzaG93bGVnZW5kID0gRkFMU0UpCiAgCiMgQWRkIHZlcnRpY2FscyAob25lIHBlciBtZXNoLCBlbnZlbG9wZSBvZiBhbGwgZnVuY3Rpb25zKQppZiAoIWlzLm51bGwodmVydGljYWxfZmluZXIpKSB7CiAgcCA8LSBwICU+JQogICAgYWRkX3RyYWNlKGRhdGEgPSB2ZXJ0aWNhbF9maW5lciwKICAgICAgICAgICAgICB4ID0gfngsIHkgPSB+eSwgeiA9IH56LAogICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiZ3JheSIsIHdpZHRoID0gMC41KSwKICAgICAgICAgICAgICBuYW1lID0gIlZlcnRpY2FsIGZpbmVyIiwgc2hvd2xlZ2VuZCA9IEZBTFNFKQp9CmlmICghaXMubnVsbCh2ZXJ0aWNhbF9jb2Fyc2VyKSkgewogIHAgPC0gcCAlPiUKICAgIGFkZF90cmFjZShkYXRhID0gdmVydGljYWxfY29hcnNlciwKICAgICAgICAgICAgICB4ID0gfngsIHkgPSB+eSwgeiA9IH56LAogICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiZ3JheSIsIHdpZHRoID0gMC41KSwKICAgICAgICAgICAgICBuYW1lID0gIlZlcnRpY2FsIGNvYXJzZXIiLCBzaG93bGVnZW5kID0gRkFMU0UpCn0KCiAgCiAgZnJhbWVfbmFtZSA8LSBkZXBhcnNlKHN1YnN0aXR1dGUoZnJhbWVfdmFsX3RvX2Rpc3BsYXkpKQogIAogIHAgPC0gcCAlPiUKICAgIGxheW91dCgKICAgICAgc2NlbmUgPSBnbG9iYWwuc2NlbmUuc2V0dGVyKHhfcmFuZ2UsIHlfcmFuZ2UsIHpfcmFuZ2UpLAogICAgICB1cGRhdGVtZW51cyA9IGxpc3QobGlzdCgKICAgICAgICB0eXBlID0gImJ1dHRvbnMiLCBzaG93YWN0aXZlID0gRkFMU0UsCiAgICAgICAgYnV0dG9ucyA9IGxpc3QoCiAgICAgICAgICBsaXN0KGxhYmVsID0gIlBsYXkiLCBtZXRob2QgPSAiYW5pbWF0ZSIsCiAgICAgICAgICAgICAgIGFyZ3MgPSBsaXN0KE5VTEwsIGxpc3QoZnJhbWUgPSBsaXN0KGR1cmF0aW9uID0gMjAwMCAvIGxlbmd0aCh0aW1lX3NlcSksIHJlZHJhdyA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyb21jdXJyZW50ID0gVFJVRSkpKSwKICAgICAgICAgIGxpc3QobGFiZWwgPSAiUGF1c2UiLCBtZXRob2QgPSAiYW5pbWF0ZSIsCiAgICAgICAgICAgICAgIGFyZ3MgPSBsaXN0KE5VTEwsIGxpc3QobW9kZSA9ICJpbW1lZGlhdGUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcmFtZSA9IGxpc3QoZHVyYXRpb24gPSAwKSwgcmVkcmF3ID0gRkFMU0UpKSkKICAgICAgICApCiAgICAgICkpLAogICAgICB0aXRsZSA9IHBhc3RlMChmcmFtZV9uYW1lLCAiOiAiLCBmb3JtYXRDKGZyYW1lX3ZhbF90b19kaXNwbGF5WzFdLCBmb3JtYXQgPSAiZiIsIGRpZ2l0cyA9IDQpKQogICAgKSAlPiUKICAgIHBsb3RseV9idWlsZCgpCiAgCiAgIyBVcGRhdGUgZnJhbWUgdGl0bGVzCiAgZm9yIChpIGluIHNlcV9hbG9uZyhwJHgkZnJhbWVzKSkgewogICAgcCR4JGZyYW1lc1tbaV1dJGxheW91dCA8LSBsaXN0KAogICAgICB0aXRsZSA9IHBhc3RlMChmcmFtZV9uYW1lLCAiOiAiLCBmb3JtYXRDKGZyYW1lX3ZhbF90b19kaXNwbGF5W2ldLCBmb3JtYXQgPSAiZiIsIGRpZ2l0cyA9IDQpKQogICAgKQogIH0KICAKICByZXR1cm4ocCkKfQoKYGBgCgojIyMgRnVuY3Rpb24gYHBsb3RfM2RfcmVjdGFuZ2xlX3NjYXR0ZXIoKWAKCmBgYHtyfQpwbG90XzNkX3JlY3RhbmdsZV9zY2F0dGVyIDwtIGZ1bmN0aW9uKGxvYywgZWlndmFscywgZWlnZnVuY3MsIGZpeGVkX2NvbG9yc2NhbGUgPSBUUlVFKSB7CgogIHggPC0gbG9jWywxXQogIHkgPC0gbG9jWywyXQoKICBjb2xvcnNjYWxlIDwtICJWaXJpZGlzIgoKICAjIENvbXB1dGUgZ2xvYmFsIGNvbG9yIGxpbWl0cyBpZiBmaXhlZAogIGlmIChmaXhlZF9jb2xvcnNjYWxlKSB7CiAgICBjbWluIDwtIG1pbihlaWdmdW5jcykKICAgIGNtYXggPC0gbWF4KGVpZ2Z1bmNzKQogIH0gZWxzZSB7CiAgICBjbWluIDwtIE5VTEwKICAgIGNtYXggPC0gTlVMTAogIH0KCiAgIyBGcmFtZXMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogIGZyYW1lcyA8LSBsYXBwbHkoc2VxX2xlbihuY29sKGVpZ2Z1bmNzKSksIGZ1bmN0aW9uKGkpIHsKICAgIGxpc3QoCiAgICAgIG5hbWUgPSBhcy5jaGFyYWN0ZXIoaSksCiAgICAgIGRhdGEgPSBsaXN0KGxpc3QoCiAgICAgICAgeCA9IHgsCiAgICAgICAgeSA9IHksCiAgICAgICAgeiA9IGVpZ2Z1bmNzWyxpXSwKICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsCiAgICAgICAgbW9kZSA9ICJtYXJrZXJzIiwKICAgICAgICBtYXJrZXIgPSBsaXN0KAogICAgICAgICAgc2l6ZSA9IDUsCiAgICAgICAgICBjb2xvciA9IGVpZ2Z1bmNzWyxpXSwKICAgICAgICAgIGNvbG9yc2NhbGUgPSBjb2xvcnNjYWxlLAogICAgICAgICAgc2hvd3NjYWxlID0gVFJVRSwKICAgICAgICAgIGNtaW4gPSBjbWluLAogICAgICAgICAgY21heCA9IGNtYXgKICAgICAgICApCiAgICAgICkpCiAgICApCiAgfSkKCiAgIyBJbml0aWFsIFBsb3QgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogIHAgPC0gcGxvdF9seSgKICAgIHggPSB4LAogICAgeSA9IHksCiAgICB6ID0gZWlnZnVuY3NbLDFdLAogICAgdHlwZSA9ICJzY2F0dGVyM2QiLAogICAgbW9kZSA9ICJtYXJrZXJzIiwKICAgIG1hcmtlciA9IGxpc3QoCiAgICAgIHNpemUgPSA1LAogICAgICBjb2xvciA9IGVpZ2Z1bmNzWywxXSwKICAgICAgY29sb3JzY2FsZSA9IGNvbG9yc2NhbGUsCiAgICAgIHNob3dzY2FsZSA9IFRSVUUsCiAgICAgIGNtaW4gPSBjbWluLAogICAgICBjbWF4ID0gY21heAogICAgKSwKICAgIGZyYW1lID0gIjEiCiAgKQoKICBwJHgkZnJhbWVzIDwtIGZyYW1lcwoKICB6X3JhbmdlIDwtIHJhbmdlKGVpZ2Z1bmNzKQogIHhfcmFuZ2UgPC0gcmFuZ2UoeCkKICB5X3JhbmdlIDwtIHJhbmdlKHkpCgogIGZyYW1lX25hbWUgPC0gZGVwYXJzZShzdWJzdGl0dXRlKGVpZ3ZhbHMpKQoKICAjIExheW91dCArIHNsaWRlciAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgcCA8LSBwICU+JSBsYXlvdXQoCiAgICB0aXRsZSA9IHBhc3RlMChmcmFtZV9uYW1lLCAiOiAiLCBlaWd2YWxzWzFdKSwKICAgIHNjZW5lID0gZ2xvYmFsLnNjZW5lLnNldHRlci5yZWMuYW5kLnNwaGVyZSh4X3JhbmdlLCB5X3JhbmdlLCB6X3JhbmdlKSwKICAgIHNsaWRlcnMgPSBsaXN0KAogICAgICBsaXN0KAogICAgICAgIGFjdGl2ZSA9IDAsCiAgICAgICAgY3VycmVudHZhbHVlID0gbGlzdChwcmVmaXggPSAiRnJhbWU6ICIpLAogICAgICAgIHBhZCA9IGxpc3QodCA9IDUwKSwKICAgICAgICBzdGVwcyA9IGxhcHBseShzZXFfbGVuKG5jb2woZWlnZnVuY3MpKSwgZnVuY3Rpb24oaSkgewogICAgICAgICAgbGlzdCgKICAgICAgICAgICAgbGFiZWwgPSBhcy5jaGFyYWN0ZXIoaSksCiAgICAgICAgICAgIG1ldGhvZCA9ICJhbmltYXRlIiwKICAgICAgICAgICAgYXJncyA9IGxpc3QoCiAgICAgICAgICAgICAgbGlzdChhcy5jaGFyYWN0ZXIoaSkpLAogICAgICAgICAgICAgIGxpc3QoZnJhbWUgPSBsaXN0KGR1cmF0aW9uID0gMzAwLCByZWRyYXcgPSBUUlVFKSwKICAgICAgICAgICAgICAgICAgIG1vZGUgPSAiaW1tZWRpYXRlIikKICAgICAgICAgICAgKQogICAgICAgICAgKQogICAgICAgIH0pCiAgICAgICkKICAgICksCiAgICB1cGRhdGVtZW51cyA9IGxpc3QoCiAgICAgIGxpc3QoCiAgICAgICAgdHlwZSA9ICJidXR0b25zIiwKICAgICAgICBzaG93YWN0aXZlID0gRkFMU0UsCiAgICAgICAgeSA9IDEsCiAgICAgICAgeCA9IDEuMTUsCiAgICAgICAgeGFuY2hvciA9ICJyaWdodCIsCiAgICAgICAgeWFuY2hvciA9ICJ0b3AiLAogICAgICAgIGJ1dHRvbnMgPSBsaXN0KAogICAgICAgICAgbGlzdChsYWJlbCA9ICJQbGF5IiwKICAgICAgICAgICAgICAgbWV0aG9kID0gImFuaW1hdGUiLAogICAgICAgICAgICAgICBhcmdzID0gbGlzdCgKICAgICAgICAgICAgICAgICBOVUxMLAogICAgICAgICAgICAgICAgIGxpc3QoZnJhbWUgPSBsaXN0KGR1cmF0aW9uID0gMzAwLCByZWRyYXcgPSBUUlVFKSwKICAgICAgICAgICAgICAgICAgICAgIGZyb21jdXJyZW50ID0gVFJVRSkKICAgICAgICAgICAgICAgKSksCiAgICAgICAgICBsaXN0KGxhYmVsID0gIlBhdXNlIiwKICAgICAgICAgICAgICAgbWV0aG9kID0gImFuaW1hdGUiLAogICAgICAgICAgICAgICBhcmdzID0gbGlzdChOVUxMLCBsaXN0KGZyYW1lID0gbGlzdChkdXJhdGlvbiA9IDApKSkpCiAgICAgICAgKQogICAgICApCiAgICApCiAgKSAlPiUgcGxvdGx5X2J1aWxkKCkKCiAgIyBVcGRhdGUgdGl0bGUgaW4gZWFjaCBmcmFtZSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogIGZvciAoaSBpbiBzZXFfbGVuKG5jb2woZWlnZnVuY3MpKSkgewogICAgcCR4JGZyYW1lc1tbaV1dJGxheW91dCA8LSBsaXN0KAogICAgICB0aXRsZSA9IHBhc3RlMChmcmFtZV9uYW1lLCAiOiAiLCBlaWd2YWxzW2ldKQogICAgKQogIH0KCiAgcmV0dXJuKHApCn0KCnBsb3RfM2RfcmVjdGFuZ2xlX29uZWNvbF92aXIgPC0gZnVuY3Rpb24obWVzaCwgZnZhbHMpIHsKICAKICBjb2xvcnNjYWxlID0gIlZpcmlkaXMiCiAgb3BhY2l0eSA9IDEKICAKICAKICB4IDwtIG1lc2gkbG9jWywgMV0KICB5IDwtIG1lc2gkbG9jWywgMl0KICB0cmkgPC0gbWVzaCRncmFwaCR0dgogIAogICMgZnZhbHMgaXMgYSB2ZWN0b3IKICAKICB4X3JhbmdlIDwtIHJhbmdlKHgpCiAgeV9yYW5nZSA8LSByYW5nZSh5KQogIHpfcmFuZ2UgPC0gcmFuZ2UoZnZhbHMpCiAgCiAgIyBTdGF0aWMgbWVzaDNkIHBsb3QKICBwIDwtIHBsb3RfbHkoCiAgICB4ID0geCwgeSA9IHksIHogPSBmdmFscywKICAgIGkgPSB0cmlbLDFdIC0gMSwKICAgIGogPSB0cmlbLDJdIC0gMSwKICAgIGsgPSB0cmlbLDNdIC0gMSwKICAgIHR5cGUgPSAibWVzaDNkIiwKICAgIGludGVuc2l0eSA9IGZ2YWxzLAogICAgY29sb3JzY2FsZSA9IGNvbG9yc2NhbGUsCiAgICBvcGFjaXR5ID0gb3BhY2l0eSwKICAgIGZsYXRzaGFkaW5nID0gVFJVRSwKICAgIHRleHQgPSBwYXN0ZTAoCiAgICAgICJ4OiAiLCBzcHJpbnRmKCIlLjNmIiwgeCksICI8YnI+IiwKICAgICAgInk6ICIsIHNwcmludGYoIiUuM2YiLCB5KSwgIjxicj4iLAogICAgICAiZjogIiwgc3ByaW50ZigiJS41ZiIsIGZ2YWxzKQogICAgKSwKICAgIGhvdmVyaW5mbyA9ICJ0ZXh0IgogICkgJT4lIGxheW91dCgKICAgIHNjZW5lID0gZ2xvYmFsLnNjZW5lLnNldHRlci5yZWMuYW5kLnNwaGVyZSh4X3JhbmdlLCB5X3JhbmdlLCB6X3JhbmdlKQogICkKICAKICByZXR1cm4ocCkKfQoKcGxvdF8zZF9yZWN0YW5nbGVfb25lY29sIDwtIGZ1bmN0aW9uKG1lc2gsIGZ2YWxzKSB7CiAgCiAgb3BhY2l0eSA8LSAxCiAgc3VyZmFjZV9jb2xvciA8LSAiYmx1ZSIKICAKICB4IDwtIG1lc2gkbG9jWywgMV0KICB5IDwtIG1lc2gkbG9jWywgMl0KICB0cmkgPC0gbWVzaCRncmFwaCR0dgogIAogIHhfcmFuZ2UgPC0gcmFuZ2UoeCkKICB5X3JhbmdlIDwtIHJhbmdlKHkpCiAgel9yYW5nZSA8LSByYW5nZShmdmFscykKICAKICAjIFN0YXRpYyBtZXNoM2QgcGxvdAogIHAgPC0gcGxvdF9seSgKICAgIHggPSB4LCB5ID0geSwgeiA9IGZ2YWxzLAogICAgaSA9IHRyaVssMV0gLSAxLAogICAgaiA9IHRyaVssMl0gLSAxLAogICAgayA9IHRyaVssM10gLSAxLAogICAgdHlwZSA9ICJtZXNoM2QiLAogICAgY29sb3IgPSBzdXJmYWNlX2NvbG9yLCAgICAgIyBGaXhlZCBibHVlIGNvbG9yCiAgICBvcGFjaXR5ID0gb3BhY2l0eSwKICAgIGZsYXRzaGFkaW5nID0gVFJVRSwKICAgIHRleHQgPSBwYXN0ZTAoCiAgICAgICJ4OiAiLCBzcHJpbnRmKCIlLjNmIiwgeCksICI8YnI+IiwKICAgICAgInk6ICIsIHNwcmludGYoIiUuM2YiLCB5KSwgIjxicj4iLAogICAgICAiZjogIiwgc3ByaW50ZigiJS41ZiIsIGZ2YWxzKQogICAgKSwKICAgIGhvdmVyaW5mbyA9ICJ0ZXh0IgogICkgJT4lIGxheW91dCgKICAgIHNjZW5lID0gZ2xvYmFsLnNjZW5lLnNldHRlci5yZWMuYW5kLnNwaGVyZSh4X3JhbmdlLCB5X3JhbmdlLCB6X3JhbmdlKQogICkKICAKICByZXR1cm4ocCkKfQoKcGxvdF8zZF9zcXVhcmVfbWVzaCA8LSBmdW5jdGlvbihtZXNoKSB7CiAgCiAgeCA8LSBtZXNoJGxvY1ssMV0KICB5IDwtIG1lc2gkbG9jWywyXQogICMgRmxhdCBzcXVhcmUgKG9yIHVzZSBtZXNoJGxvY1ssM10gaWYgYXZhaWxhYmxlKQogIGlmIChuY29sKG1lc2gkbG9jKSA+PSAzKSB7CiAgICB6IDwtIG1lc2gkbG9jWywzXQogIH0gZWxzZSB7CiAgICB6IDwtIHJlcCgwLCBsZW5ndGgoeCkpCiAgfQogIAogIHhfcmFuZ2UgPC0gcmFuZ2UoeCkKICB5X3JhbmdlIDwtIHJhbmdlKHkpCiAgel9yYW5nZSA8LSBjKDAsMSkKICAKICB0cmkgPC0gbWVzaCRncmFwaCR0dgogIAogICMgLS0tLSBVbmlmb3JtIGdyYXkgY29sb3IgZm9yIGZhY2VzIC0tLS0KICBncmF5X3JnYiA8LSByZXAobGlzdChyZ2IoMTgwLCAxODAsIDE4MCwgbWF4Q29sb3JWYWx1ZSA9IDI1NSkpLCBsZW5ndGgoeCkpCiAgd2hpdGVfcmdiIDwtIHJlcChsaXN0KHJnYigyNTUsIDI1NSwgMjU1LCBhbHBoYSA9IDI1NSwgbWF4Q29sb3JWYWx1ZSA9IDI1NSkpLCBsZW5ndGgoeCkpCgogIAogIGRmMyA8LSBkYXRhLmZyYW1lKHggPSB4LCAKICAgICAgICAgICAgICAgICAgeSA9IHksIAogICAgICAgICAgICAgICAgICB6ID0geikKICAKICAjIC0tLS0gUGxvdCBtZXNoIGZhY2VzIC0tLS0KICBwIDwtIHBsb3RfbHkoKSAlPiUgYWRkX3RyYWNlKAogICAgeCA9IHgsCiAgICB5ID0geSwKICAgIHogPSB6LAogICAgaSA9IHRyaVssMV0gLSAxLAogICAgaiA9IHRyaVssMl0gLSAxLAogICAgayA9IHRyaVssM10gLSAxLAogICAgdHlwZSA9ICJtZXNoM2QiLAogICAgdmVydGV4Y29sb3IgPSB3aGl0ZV9yZ2IsCiAgICBmbGF0c2hhZGluZyA9IFRSVUUsCiAgICBvcGFjaXR5ID0gMSwKICAgIHNob3dzY2FsZSA9IEZBTFNFCiAgKQogIAogICMgLS0tLSBFeHRyYWN0IHVuaXF1ZSBlZGdlcyAtLS0tCiAgZWRnZXMgPC0gcmJpbmQoCiAgICB0cmlbLDE6Ml0sCiAgICB0cmlbLDI6M10sCiAgICB0cmlbLGMoMywxKV0KICApCiAgZWRnZXMgPC0gdChhcHBseShlZGdlcywgMSwgc29ydCkpCiAgZWRnZXMgPC0gdW5pcXVlKGVkZ2VzKQogIAogICMgLS0tLSBFZmZpY2llbnQgZWRnZXMgYXMgb25lIHRyYWNlIHVzaW5nIE5BIGJyZWFrcyAtLS0tCiAgeF9lZGdlcyA8LSBhcy52ZWN0b3IodChjYmluZCh4W2VkZ2VzWywxXV0sIHhbZWRnZXNbLDJdXSwgTkEpKSkKICB5X2VkZ2VzIDwtIGFzLnZlY3Rvcih0KGNiaW5kKHlbZWRnZXNbLDFdXSwgeVtlZGdlc1ssMl1dLCBOQSkpKQogIHpfZWRnZXMgPC0gYXMudmVjdG9yKHQoY2JpbmQoeltlZGdlc1ssMV1dLCB6W2VkZ2VzWywyXV0sIE5BKSkpCiAgCiAgcCA8LSBhZGRfdHJhY2UoCiAgICBwLAogICAgeCA9IHhfZWRnZXMsIHkgPSB5X2VkZ2VzLCB6ID0gel9lZGdlcywKICAgIHR5cGUgPSAic2NhdHRlcjNkIiwKICAgIG1vZGUgPSAibGluZXMiLAogICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmxhY2siLCB3aWR0aCA9IDMpLAogICAgc2hvd2xlZ2VuZCA9IEZBTFNFLAogICAgaG92ZXJpbmZvID0gIm5vbmUiCiAgKSAlPiUgCiAgICBhZGRfdHJhY2UoZGF0YSA9IGRmMywgeCA9IH54LCB5ID0gfnksIHogPSB+eiwgbW9kZSA9ICJtYXJrZXJzIiwgdHlwZSA9ICJzY2F0dGVyM2QiLCAKICAgICAgICAgICAgbWFya2VyID0gbGlzdChzaXplID0gNCwgY29sb3IgPSAiZ3JheSIsIHN5bWJvbCA9IDEwNCkpICU+JQogICAgbGF5b3V0KAogICAgc2NlbmUgPSBnbG9iYWwuc2NlbmUuc2V0dGVyLnJlYy5hbmQuc3BoZXJlKHhfcmFuZ2UsIHlfcmFuZ2UsIHpfcmFuZ2UpCiAgKQogIAogIHJldHVybihwKQp9CgoKcGxvdF8zZF9zcXVhcmVfbWVzaF93aXRoX2hhdCA8LSBmdW5jdGlvbihtZXNoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhhdF9ub2RlcyA9IE5VTEwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGF0X2hlaWdodCA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGF0X2NvbG9yID0gTlVMTCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoYXRfYWxwaGEgPSAwLjYpIHsKICAKICB4IDwtIG1lc2gkbG9jWywxXQogIHkgPC0gbWVzaCRsb2NbLDJdCiAgbiA8LSBsZW5ndGgoeCkKICAKICB6MCA8LSByZXAoMCwgbikKICB0cmkgPC0gbWVzaCRncmFwaCR0dgogIAogIHhfcmFuZ2UgPC0gcmFuZ2UoeCkKICB5X3JhbmdlIDwtIHJhbmdlKHkpCiAgel9yYW5nZSA8LSBjKDAsIGhhdF9oZWlnaHQpCiAgCiAgIyAtLS0tIEV4dHJhY3QgdW5pcXVlIGVkZ2VzIG9uY2UgLS0tLQogIGVkZ2VzIDwtIHJiaW5kKHRyaVssMToyXSwgdHJpWywyOjNdLCB0cmlbLGMoMywxKV0pCiAgZWRnZXMgPC0gdChhcHBseShlZGdlcywgMSwgc29ydCkpCiAgZWRnZXMgPC0gdW5pcXVlKGVkZ2VzKQogIAogICMgQmFzZSBtZXNoIGNvbG9yCiAgd2hpdGVfcmdiIDwtIHJlcChsaXN0KHJnYigyNTUsIDI1NSwgMjU1LCBhbHBoYSA9IDI1NSwgbWF4Q29sb3JWYWx1ZSA9IDI1NSkpLCBuKQogIAogIGRmMyA8LSBkYXRhLmZyYW1lKHggPSB4LCAKICAgICAgICAgICAgICAgICAgeSA9IHksIAogICAgICAgICAgICAgICAgICB6ID0gcmVwKDAsIGxlbmd0aCh4KSkpCiAgCiAgIyAtLS0tIEJhc2UgbWVzaCAtLS0tCiAgcCA8LSBwbG90X2x5KCkgJT4lIGFkZF90cmFjZSgKICAgIHggPSB4LCB5ID0geSwgeiA9IHowLAogICAgaSA9IHRyaVssMV0gLSAxLCBqID0gdHJpWywyXSAtIDEsIGsgPSB0cmlbLDNdIC0gMSwKICAgIHR5cGUgPSAibWVzaDNkIiwKICAgIHZlcnRleGNvbG9yID0gd2hpdGVfcmdiLAogICAgZmxhdHNoYWRpbmcgPSBUUlVFLAogICAgb3BhY2l0eSA9IDEsCiAgICBuYW1lID0gIk1lc2giCiAgKQogIAogICMgLS0tLSBCYXNlIG1lc2ggZWRnZXMgLS0tLQogIHhfZWRnZXMgPC0gYXMudmVjdG9yKHQoY2JpbmQoeFtlZGdlc1ssMV1dLCB4W2VkZ2VzWywyXV0sIE5BKSkpCiAgeV9lZGdlcyA8LSBhcy52ZWN0b3IodChjYmluZCh5W2VkZ2VzWywxXV0sIHlbZWRnZXNbLDJdXSwgTkEpKSkKICB6X2VkZ2VzIDwtIGFzLnZlY3Rvcih0KGNiaW5kKHowW2VkZ2VzWywxXV0sIHowW2VkZ2VzWywyXV0sIE5BKSkpCiAgCiAgIyAtLS0tIEhhdCBmdW5jdGlvbnMgLS0tLQogIGlmICghaXMubnVsbChoYXRfbm9kZXMpKSB7CiAgICAKICAgIGlmIChpcy5udWxsKGhhdF9jb2xvcikpIHsKICAgICAgaGF0X2NvbG9yIDwtIGdyRGV2aWNlczo6cmFpbmJvdyhsZW5ndGgoaGF0X25vZGVzKSkKICAgIH0KICAgIGlmIChsZW5ndGgoaGF0X2NvbG9yKSAhPSBsZW5ndGgoaGF0X25vZGVzKSkgewogICAgICBzdG9wKCJsZW5ndGgoaGF0X2NvbG9yKSBtdXN0IGVxdWFsIGxlbmd0aChoYXRfbm9kZXMpIikKICAgIH0KICAgIAogICAgZm9yIChrIGluIHNlcV9hbG9uZyhoYXRfbm9kZXMpKSB7CiAgICAgIGkwIDwtIGhhdF9ub2Rlc1trXQogICAgICAKICAgICAgcGhpIDwtIHJlcCgwLCBuKQogICAgICBwaGlbaTBdIDwtIDEKICAgICAgel9oYXQgPC0gaGF0X2hlaWdodCAqIHBoaQogICAgICAKICAgICAgaGF0X3JnYiA8LSByZXAobGlzdChhZGp1c3Rjb2xvcihoYXRfY29sb3Jba10sIGFscGhhLmYgPSBoYXRfYWxwaGEpKSwgbikKICAgICAgCiAgICAgICMgLS0tLSBIYXQgc3VyZmFjZSAtLS0tCiAgICAgIHAgPC0gYWRkX3RyYWNlKHAsCiAgICAgICAgeCA9IHgsIHkgPSB5LCB6ID0gel9oYXQsCiAgICAgICAgaSA9IHRyaVssMV0gLSAxLCBqID0gdHJpWywyXSAtIDEsIGsgPSB0cmlbLDNdIC0gMSwKICAgICAgICB0eXBlID0gIm1lc2gzZCIsCiAgICAgICAgdmVydGV4Y29sb3IgPSBoYXRfcmdiLAogICAgICAgIGZsYXRzaGFkaW5nID0gVFJVRSwKICAgICAgICBvcGFjaXR5ID0gMSwKICAgICAgICBuYW1lID0gcGFzdGUwKCJwaGlfIiwgaTApLAogICAgICAgIHNob3dsZWdlbmQgPSBGQUxTRQogICAgICApCiAgICAgIAogICAgICAjIC0tLS0gSGF0IGVkZ2VzICh3aXJlZnJhbWUpIC0tLS0KICAgICAgeF9oYXRfZWRnZXMgPC0gYXMudmVjdG9yKHQoY2JpbmQoeFtlZGdlc1ssMV1dLCB4W2VkZ2VzWywyXV0sIE5BKSkpCiAgICAgIHlfaGF0X2VkZ2VzIDwtIGFzLnZlY3Rvcih0KGNiaW5kKHlbZWRnZXNbLDFdXSwgeVtlZGdlc1ssMl1dLCBOQSkpKQogICAgICB6X2hhdF9lZGdlcyA8LSBhcy52ZWN0b3IodChjYmluZCh6X2hhdFtlZGdlc1ssMV1dLCB6X2hhdFtlZGdlc1ssMl1dLCBOQSkpKQogICAgICAKICAgICAgcCA8LSBhZGRfdHJhY2UocCwKICAgICAgICB4ID0geF9oYXRfZWRnZXMsIHkgPSB5X2hhdF9lZGdlcywgeiA9IHpfaGF0X2VkZ2VzLAogICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSBoYXRfY29sb3Jba10sIHdpZHRoID0gMiksCiAgICAgICAgc2hvd2xlZ2VuZCA9IEZBTFNFCiAgICAgICkKICAgICAgCiAgICAgICMgIyBNYXJrIGhhdCBjZW50ZXIgbm9kZQogICAgICAjIHAgPC0gYWRkX3RyYWNlKHAsCiAgICAgICMgICB4ID0geFtpMF0sIHkgPSB5W2kwXSwgeiA9IHpfaGF0W2kwXSwKICAgICAgIyAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJtYXJrZXJzIiwKICAgICAgIyAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDgsIGNvbG9yID0gaGF0X2NvbG9yW2tdKSwKICAgICAgIyAgIG5hbWUgPSBwYXN0ZTAoIm5vZGVfIiwgaTApCiAgICAgICMgKQogICAgfQogIH0KICAKICBwIDwtIGFkZF90cmFjZShwLAogICAgeCA9IHhfZWRnZXMsIHkgPSB5X2VkZ2VzLCB6ID0gel9lZGdlcywKICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibGFjayIsIHdpZHRoID0gNCksCiAgICBzaG93bGVnZW5kID0gRkFMU0UKICApCiAgCiAgcCA8LSBwICU+JSAKICAgIGFkZF90cmFjZShkYXRhID0gZGYzLCB4ID0gfngsIHkgPSB+eSwgeiA9IH56LCBtb2RlID0gIm1hcmtlcnMiLCB0eXBlID0gInNjYXR0ZXIzZCIsIAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KHNpemUgPSA0LCBjb2xvciA9ICJncmF5Iiwgc3ltYm9sID0gMTA0KSwgc2hvd2xlZ2VuZCA9IEZBTFNFKSAlPiUgCiAgICBsYXlvdXQoCiAgICBzY2VuZSA9IGdsb2JhbC5zY2VuZS5zZXR0ZXIucmVjLmFuZC5zcGhlcmUuZm9yLmhhdCh4X3JhbmdlLCB5X3JhbmdlLCB6X3JhbmdlKQogICkKICAKICByZXR1cm4ocCkKfQoKCmBgYAoKCgojIyMgRnVuY3Rpb24gYHBsb3RfM2Rfc3BoZXJlX3NjYXR0ZXIoKWAKCmBgYHtyfQpwbG90XzNkX3NwaGVyZV9zY2F0dGVyIDwtIGZ1bmN0aW9uKG1lc2gsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlaWd2YWxzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWlnZnVuY3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpeGVkX2NvbG9yc2NhbGUgPSBUUlVFKSB7CiAgCiAgY29sb3JzY2FsZSA9ICJWaXJpZGlzIgogIAogIHggPC0gbWVzaCRsb2NbLDFdCiAgeSA8LSBtZXNoJGxvY1ssMl0KICB6IDwtIG1lc2gkbG9jWywzXQogIAogICMgQ29tcHV0ZSBnbG9iYWwgY29sb3IgbGltaXRzIGlmIGZpeGVkCiAgaWYgKGZpeGVkX2NvbG9yc2NhbGUpIHsKICAgIGNtaW4gPC0gbWluKGVpZ2Z1bmNzKQogICAgY21heCA8LSBtYXgoZWlnZnVuY3MpCiAgfSBlbHNlIHsKICAgIGNtaW4gPC0gTlVMTAogICAgY21heCA8LSBOVUxMCiAgfQogIAogICMgQ3JlYXRlIGZyYW1lcwogIGZyYW1lcyA8LSBsYXBwbHkoc2VxX2xlbihuY29sKGVpZ2Z1bmNzKSksIGZ1bmN0aW9uKGkpIHsKICAgIAogICAgZnZhbHMgPC0gZWlnZnVuY3NbLGldCiAgICAKICAgIGxpc3QoCiAgICAgIG5hbWUgPSBhcy5jaGFyYWN0ZXIoaSksCiAgICAgIGRhdGEgPSBsaXN0KGxpc3QoCiAgICAgICAgeCA9IHgsCiAgICAgICAgeSA9IHksCiAgICAgICAgeiA9IHosCiAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLAogICAgICAgIG1vZGUgPSAibWFya2VycyIsCiAgICAgICAgbWFya2VyID0gbGlzdCgKICAgICAgICAgIHNpemUgPSA1LAogICAgICAgICAgY29sb3IgPSBmdmFscywKICAgICAgICAgIGNvbG9yc2NhbGUgPSBjb2xvcnNjYWxlLAogICAgICAgICAgc2hvd3NjYWxlID0gVFJVRSwKICAgICAgICAgIGNtaW4gPSBjbWluLAogICAgICAgICAgY21heCA9IGNtYXgKICAgICAgICApLAogICAgICAgIHRleHQgPSBwYXN0ZTAoCiAgICAgICAgICAieDogIiwgc3ByaW50ZigiJS4zZiIsIHgpLCAiPGJyPiIsCiAgICAgICAgICAieTogIiwgc3ByaW50ZigiJS4zZiIsIHkpLCAiPGJyPiIsCiAgICAgICAgICAiejogIiwgc3ByaW50ZigiJS4zZiIsIHopLCAiPGJyPiIsCiAgICAgICAgICAiZjogIiwgc3ByaW50ZigiJS41ZiIsIGZ2YWxzKQogICAgICAgICksCiAgICAgICAgaG92ZXJpbmZvID0gInRleHQiCiAgICAgICkpCiAgICApCiAgfSkKICAKICAjIEluaXRpYWwgcGxvdCAoZnJhbWUgMSkKICBmdmFsczAgPC0gZWlnZnVuY3NbLDFdCiAgCiAgcCA8LSBwbG90X2x5KAogICAgeCA9IHgsIHkgPSB5LCB6ID0geiwKICAgIHR5cGUgPSAic2NhdHRlcjNkIiwKICAgIG1vZGUgPSAibWFya2VycyIsCiAgICBtYXJrZXIgPSBsaXN0KAogICAgICBzaXplID0gNSwKICAgICAgY29sb3IgPSBmdmFsczAsCiAgICAgIGNvbG9yc2NhbGUgPSBjb2xvcnNjYWxlLAogICAgICBzaG93c2NhbGUgPSBUUlVFLAogICAgICBjbWluID0gY21pbiwKICAgICAgY21heCA9IGNtYXgKICAgICksCiAgICB0ZXh0ID0gcGFzdGUwKAogICAgICAieDogIiwgc3ByaW50ZigiJS4zZiIsIHgpLCAiPGJyPiIsCiAgICAgICJ5OiAiLCBzcHJpbnRmKCIlLjNmIiwgeSksICI8YnI+IiwKICAgICAgIno6ICIsIHNwcmludGYoIiUuM2YiLCB6KSwgIjxicj4iLAogICAgICAiZjogIiwgc3ByaW50ZigiJS41ZiIsIGZ2YWxzMCkKICAgICksCiAgICBob3ZlcmluZm8gPSAidGV4dCIsCiAgICBmcmFtZSA9ICIxIgogICkKICAKICBmcmFtZV9uYW1lIDwtIGRlcGFyc2Uoc3Vic3RpdHV0ZShlaWd2YWxzKSkKICAKICBwJHgkZnJhbWVzIDwtIGZyYW1lcwogIAogIHhfcmFuZ2UgPC0gcmFuZ2UoeCkKICB5X3JhbmdlIDwtIHJhbmdlKHkpCiAgel9yYW5nZSA8LSByYW5nZSh6KQogIAogICMgU2xpZGVyICsgcGxheS9wYXVzZQogIHAgPC0gcCAlPiUgbGF5b3V0KAogICAgdGl0bGUgPSBwYXN0ZTAoZnJhbWVfbmFtZSwgIjogIiwgZWlndmFsc1sxXSksCiAgICBzY2VuZSA9IGdsb2JhbC5zY2VuZS5zZXR0ZXIucmVjLmFuZC5zcGhlcmUoeF9yYW5nZSwgeV9yYW5nZSwgel9yYW5nZSksCiAgICBzbGlkZXJzID0gbGlzdCgKICAgICAgbGlzdCgKICAgICAgICBhY3RpdmUgPSAwLAogICAgICAgIGN1cnJlbnR2YWx1ZSA9IGxpc3QocHJlZml4ID0gIk1vZGU6ICIpLAogICAgICAgIHBhZCA9IGxpc3QodCA9IDUwKSwKICAgICAgICBzdGVwcyA9IGxhcHBseShzZXFfbGVuKG5jb2woZWlnZnVuY3MpKSwgZnVuY3Rpb24oaSkgewogICAgICAgICAgbGlzdCgKICAgICAgICAgICAgbGFiZWwgPSBhcy5jaGFyYWN0ZXIoaSksCiAgICAgICAgICAgIG1ldGhvZCA9ICJhbmltYXRlIiwKICAgICAgICAgICAgYXJncyA9IGxpc3QobGlzdChhcy5jaGFyYWN0ZXIoaSkpLAogICAgICAgICAgICAgICAgICAgICAgICBsaXN0KG1vZGUgPSAiaW1tZWRpYXRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcmFtZSA9IGxpc3QoZHVyYXRpb24gPSAzMDAsIHJlZHJhdyA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYW5zaXRpb24gPSBsaXN0KGR1cmF0aW9uID0gMCkpKQogICAgICAgICAgKQogICAgICAgIH0pCiAgICAgICkKICAgICksCiAgICB1cGRhdGVtZW51cyA9IGxpc3QoCiAgICAgIGxpc3QoCiAgICAgICAgdHlwZSA9ICJidXR0b25zIiwKICAgICAgICBzaG93YWN0aXZlID0gRkFMU0UsCiAgICAgICAgeSA9IDEsCiAgICAgICAgeCA9IDEuMTUsCiAgICAgICAgeGFuY2hvciA9ICJyaWdodCIsCiAgICAgICAgeWFuY2hvciA9ICJ0b3AiLAogICAgICAgIGJ1dHRvbnMgPSBsaXN0KAogICAgICAgICAgbGlzdCgKICAgICAgICAgICAgbGFiZWwgPSAiUGxheSIsCiAgICAgICAgICAgIG1ldGhvZCA9ICJhbmltYXRlIiwKICAgICAgICAgICAgYXJncyA9IGxpc3QoTlVMTCwKICAgICAgICAgICAgICAgICAgICAgICAgbGlzdChmcmFtZSA9IGxpc3QoZHVyYXRpb24gPSAzMDAsIHJlZHJhdyA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyb21jdXJyZW50ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlID0gImltbWVkaWF0ZSIpKQogICAgICAgICAgKSwKICAgICAgICAgIGxpc3QoCiAgICAgICAgICAgIGxhYmVsID0gIlBhdXNlIiwKICAgICAgICAgICAgbWV0aG9kID0gImFuaW1hdGUiLAogICAgICAgICAgICBhcmdzID0gbGlzdChOVUxMLAogICAgICAgICAgICAgICAgICAgICAgICBsaXN0KGZyYW1lID0gbGlzdChkdXJhdGlvbiA9IDAsIHJlZHJhdyA9IEZBTFNFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlID0gImltbWVkaWF0ZSIpKQogICAgICAgICAgKQogICAgICAgICkKICAgICAgKQogICAgKQogICkgJT4lIHBsb3RseV9idWlsZCgpCiAgCiAgIyBVcGRhdGUgdGl0bGUgcGVyIGZyYW1lCiAgZm9yIChpIGluIHNlcV9sZW4obmNvbChlaWdmdW5jcykpKSB7CiAgICBwJHgkZnJhbWVzW1tpXV0kbGF5b3V0IDwtIGxpc3QoCiAgICAgIHRpdGxlID0gcGFzdGUwKGZyYW1lX25hbWUsICI6ICIsIGVpZ3ZhbHNbaV0pCiAgICApCiAgfQogIAogIHJldHVybihwKQp9CgpwbG90XzNkX3NsaWRlcl9zcGhlcmUgPC0gZnVuY3Rpb24obWVzaCwgZWlndmFscywgZWlnZnVuY3MsIGZpeGVkX2NvbG9yc2NhbGUgPSBUUlVFKSB7CiAgCiAgY29sb3JzY2FsZSA9ICJWaXJpZGlzIgogIG9wYWNpdHkgPSAxCiAgCiAgeCA8LSBtZXNoJGxvY1ssIDFdCiAgeSA8LSBtZXNoJGxvY1ssIDJdCiAgeiA8LSBtZXNoJGxvY1ssIDNdCiAgdHJpIDwtIG1lc2gkZ3JhcGgkdHYKICAKICAjIEdsb2JhbCBpbnRlbnNpdHkgbGltaXRzIGlmIGZpeGVkCiAgaWYgKGZpeGVkX2NvbG9yc2NhbGUpIHsKICAgIGNtaW4gPC0gbWluKGVpZ2Z1bmNzKQogICAgY21heCA8LSBtYXgoZWlnZnVuY3MpCiAgfSBlbHNlIHsKICAgIGNtaW4gPC0gTlVMTAogICAgY21heCA8LSBOVUxMCiAgfQogIAogICMgQ3JlYXRlIGZyYW1lcwogIGZyYW1lcyA8LSBsYXBwbHkoc2VxX2xlbihuY29sKGVpZ2Z1bmNzKSksIGZ1bmN0aW9uKGkpIHsKICAgIGZ2YWxzIDwtIGVpZ2Z1bmNzWyxpXQogICAgbGlzdCgKICAgICAgbmFtZSA9IGFzLmNoYXJhY3RlcihpKSwKICAgICAgZGF0YSA9IGxpc3QobGlzdCgKICAgICAgICB4ID0geCwKICAgICAgICB5ID0geSwKICAgICAgICB6ID0geiwKICAgICAgICBpID0gdHJpWywxXSAtIDEsCiAgICAgICAgaiA9IHRyaVssMl0gLSAxLAogICAgICAgIGsgPSB0cmlbLDNdIC0gMSwKICAgICAgICB0eXBlID0gIm1lc2gzZCIsCiAgICAgICAgaW50ZW5zaXR5ID0gZnZhbHMsCiAgICAgICAgY29sb3JzY2FsZSA9IGNvbG9yc2NhbGUsCiAgICAgICAgb3BhY2l0eSA9IG9wYWNpdHksCiAgICAgICAgZmxhdHNoYWRpbmcgPSBUUlVFLAogICAgICAgIGNtaW4gPSBjbWluLAogICAgICAgIGNtYXggPSBjbWF4LAogICAgICAgIHRleHQgPSBwYXN0ZTAoCiAgICAgICAgICAieDogIiwgc3ByaW50ZigiJS4zZiIsIHgpLCAiPGJyPiIsCiAgICAgICAgICAieTogIiwgc3ByaW50ZigiJS4zZiIsIHkpLCAiPGJyPiIsCiAgICAgICAgICAiejogIiwgc3ByaW50ZigiJS4zZiIsIHopLCAiPGJyPiIsCiAgICAgICAgICAiZjogIiwgc3ByaW50ZigiJS41ZiIsIGZ2YWxzKQogICAgICAgICksCiAgICAgICAgaG92ZXJpbmZvID0gInRleHQiCiAgICAgICkpCiAgICApCiAgfSkKICAKICAjIEluaXRpYWwgcGxvdAogIGZ2YWxzMCA8LSBlaWdmdW5jc1ssMV0KICAKICBwIDwtIHBsb3RfbHkoCiAgICB4ID0geCwgeSA9IHksIHogPSB6LAogICAgaSA9IHRyaVssMV0gLSAxLAogICAgaiA9IHRyaVssMl0gLSAxLAogICAgayA9IHRyaVssM10gLSAxLAogICAgdHlwZSA9ICJtZXNoM2QiLAogICAgaW50ZW5zaXR5ID0gZnZhbHMwLAogICAgY29sb3JzY2FsZSA9IGNvbG9yc2NhbGUsCiAgICBvcGFjaXR5ID0gb3BhY2l0eSwKICAgIGZsYXRzaGFkaW5nID0gVFJVRSwKICAgIGNtaW4gPSBjbWluLAogICAgY21heCA9IGNtYXgsCiAgICB0ZXh0ID0gcGFzdGUwKAogICAgICAieDogIiwgc3ByaW50ZigiJS4zZiIsIHgpLCAiPGJyPiIsCiAgICAgICJ5OiAiLCBzcHJpbnRmKCIlLjNmIiwgeSksICI8YnI+IiwKICAgICAgIno6ICIsIHNwcmludGYoIiUuM2YiLCB6KSwgIjxicj4iLAogICAgICAiZjogIiwgc3ByaW50ZigiJS41ZiIsIGZ2YWxzMCkKICAgICksCiAgICBjb2xvcmJhciA9IGxpc3QoCiAgICAgIHRoaWNrbmVzcyA9IDE1LAogICAgICBsZW4gPSAwLjUsICAgICAgIyBmcmFjdGlvbiBvZiB0aGUgaGVpZ2h0IG9mIHRoZSBwbG90CiAgICAgIHkgPSAwLjUsICAgICAgICAjIGNlbnRlciB2ZXJ0aWNhbGx5ICgwID0gYm90dG9tLCAxID0gdG9wKQogICAgICB5YW5jaG9yID0gIm1pZGRsZSIsCiAgICAgIHRpdGxlID0gIiIKICAgICksCiAgICBob3ZlcmluZm8gPSAidGV4dCIsCiAgICBmcmFtZSA9ICIxIgogICkKICAKICBmcmFtZV9uYW1lIDwtIGRlcGFyc2Uoc3Vic3RpdHV0ZShlaWd2YWxzKSkKICAKICBwJHgkZnJhbWVzIDwtIGZyYW1lcwogIAogICMgTGF5b3V0ICsgc2xpZGVyICsgYnV0dG9ucwogIHAgPC0gcCAlPiUgbGF5b3V0KAogICAgdGl0bGUgPSBwYXN0ZTAoZnJhbWVfbmFtZSwgIjogIiwgZWlndmFsc1sxXSksCiAgICBzY2VuZSA9IGdsb2JhbC5zY2VuZS5zZXR0ZXIucmVjLmFuZC5zcGhlcmUocmFuZ2UoeCksIHJhbmdlKHkpLCByYW5nZSh6KSksCiAgICBzbGlkZXJzID0gbGlzdCgKICAgICAgbGlzdCgKICAgICAgICBhY3RpdmUgPSAwLAogICAgICAgIGN1cnJlbnR2YWx1ZSA9IGxpc3QocHJlZml4ID0gIkZyYW1lOiAiKSwKICAgICAgICBwYWQgPSBsaXN0KHQgPSA1MCksCiAgICAgICAgc3RlcHMgPSBsYXBwbHkoc2VxX2xlbihuY29sKGVpZ2Z1bmNzKSksIGZ1bmN0aW9uKGkpIHsKICAgICAgICAgIGxpc3QoCiAgICAgICAgICAgIGxhYmVsID0gYXMuY2hhcmFjdGVyKGkpLAogICAgICAgICAgICBtZXRob2QgPSAiYW5pbWF0ZSIsCiAgICAgICAgICAgIGFyZ3MgPSBsaXN0KGxpc3QoYXMuY2hhcmFjdGVyKGkpKSwKICAgICAgICAgICAgICAgICAgICAgICAgbGlzdChtb2RlID0gImltbWVkaWF0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJhbWUgPSBsaXN0KGR1cmF0aW9uID0gMzAwLCByZWRyYXcgPSBUUlVFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2l0aW9uID0gbGlzdChkdXJhdGlvbiA9IDApKSkKICAgICAgICAgICkKICAgICAgICB9KQogICAgICApCiAgICApLAogICAgdXBkYXRlbWVudXMgPSBsaXN0KAogICAgICBsaXN0KAogICAgICAgIHR5cGUgPSAiYnV0dG9ucyIsCiAgICAgICAgc2hvd2FjdGl2ZSA9IEZBTFNFLAogICAgICAgIHkgPSAxLAogICAgICAgIHggPSAxLjE1LAogICAgICAgIHhhbmNob3IgPSAicmlnaHQiLAogICAgICAgIHlhbmNob3IgPSAidG9wIiwKICAgICAgICBidXR0b25zID0gbGlzdCgKICAgICAgICAgIGxpc3QobGFiZWwgPSAiUGxheSIsCiAgICAgICAgICAgICAgIG1ldGhvZCA9ICJhbmltYXRlIiwKICAgICAgICAgICAgICAgYXJncyA9IGxpc3QoTlVMTCwgbGlzdChmcmFtZSA9IGxpc3QoZHVyYXRpb24gPSAzMDAsIHJlZHJhdyA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyb21jdXJyZW50ID0gVFJVRSwgbW9kZSA9ICJpbW1lZGlhdGUiKSkpLAogICAgICAgICAgbGlzdChsYWJlbCA9ICJQYXVzZSIsCiAgICAgICAgICAgICAgIG1ldGhvZCA9ICJhbmltYXRlIiwKICAgICAgICAgICAgICAgYXJncyA9IGxpc3QoTlVMTCwgbGlzdChmcmFtZSA9IGxpc3QoZHVyYXRpb24gPSAwLCByZWRyYXcgPSBGQUxTRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZSA9ICJpbW1lZGlhdGUiKSkpCiAgICAgICAgKQogICAgICApCiAgICApCiAgKSAlPiUgcGxvdGx5X2J1aWxkKCkKICAKICAjIEFkZCB0aXRsZXMgZm9yIGZyYW1lcwogIGZvciAoaSBpbiBzZXFfbGVuKG5jb2woZWlnZnVuY3MpKSkgewogICAgcCR4JGZyYW1lc1tbaV1dJGxheW91dCA8LSBsaXN0KAogICAgICB0aXRsZSA9IHBhc3RlMChmcmFtZV9uYW1lLCAiOiAiLCBlaWd2YWxzW2ldKQogICAgKQogIH0KICAKICByZXR1cm4ocCkKfQoKCnBsb3RfM2Rfc3BoZXJlX3NjYXR0ZXJfb25lY29sIDwtIGZ1bmN0aW9uKG1lc2gsIGVpZ2Z1bmNzKSB7CiAgCiAgY29sb3JzY2FsZSA9ICJWaXJpZGlzIgogIAogIHggPC0gbWVzaCRsb2NbLDFdCiAgeSA8LSBtZXNoJGxvY1ssMl0KICB6IDwtIG1lc2gkbG9jWywzXQogIAogICMgZWlnZnVuY3MgaXMgYSB2ZWN0b3IKICBmdmFscyA8LSBlaWdmdW5jcwogIAogIHhfcmFuZ2UgPC0gcmFuZ2UoeCkKICB5X3JhbmdlIDwtIHJhbmdlKHkpCiAgel9yYW5nZSA8LSByYW5nZSh6KQogIAogICMgQnVpbGQgc3RhdGljIDNEIHNjYXR0ZXIgcGxvdAogIHAgPC0gcGxvdF9seSgKICAgIHggPSB4LCB5ID0geSwgeiA9IHosCiAgICB0eXBlID0gInNjYXR0ZXIzZCIsCiAgICBtb2RlID0gIm1hcmtlcnMiLAogICAgbWFya2VyID0gbGlzdCgKICAgICAgc2l6ZSA9IDUsCiAgICAgIGNvbG9yID0gZnZhbHMsCiAgICAgIGNvbG9yc2NhbGUgPSBjb2xvcnNjYWxlLAogICAgICBzaG93c2NhbGUgPSBUUlVFCiAgICApLAogICAgdGV4dCA9IHBhc3RlMCgKICAgICAgIng6ICIsIHNwcmludGYoIiUuM2YiLCB4KSwgIjxicj4iLAogICAgICAieTogIiwgc3ByaW50ZigiJS4zZiIsIHkpLCAiPGJyPiIsCiAgICAgICJ6OiAiLCBzcHJpbnRmKCIlLjNmIiwgeiksICI8YnI+IiwKICAgICAgImY6ICIsIHNwcmludGYoIiUuNWYiLCBmdmFscykKICAgICksCiAgICBob3ZlcmluZm8gPSAidGV4dCIKICApICU+JSBsYXlvdXQoCiAgICBzY2VuZSA9IGdsb2JhbC5zY2VuZS5zZXR0ZXIucmVjLmFuZC5zcGhlcmUoeF9yYW5nZSwgeV9yYW5nZSwgel9yYW5nZSkKICApCiAgCiAgcmV0dXJuKHApCn0KCgoKIyBwbG90XzNkX3NwaGVyZV9zdXJmYWNlX29uZWNvbCA8LSBmdW5jdGlvbihtZXNoLCBmdmFscykgewojICAgCiMgICBjb2xvcnNjYWxlID0gIlZpcmlkaXMiCiMgICBvcGFjaXR5ID0gMQojICAgCiMgICAKIyAgIHggPC0gbWVzaCRsb2NbLCAxXQojICAgeSA8LSBtZXNoJGxvY1ssIDJdCiMgICB6IDwtIG1lc2gkbG9jWywgM10KIyAgIHRyaSA8LSBtZXNoJGdyYXBoJHR2CiMgICAKIyAgICMgZnZhbHMgaXMgYSB2ZWN0b3IKIyAgIAojICAgeF9yYW5nZSA8LSByYW5nZSh4KQojICAgeV9yYW5nZSA8LSByYW5nZSh5KQojICAgel9yYW5nZSA8LSByYW5nZSh6KQojICAgCiMgICAjIFN0YXRpYyBtZXNoM2QgcGxvdAojICAgcCA8LSBwbG90X2x5KAojICAgICB4ID0geCwgeSA9IHksIHogPSB6LAojICAgICBpID0gdHJpWywxXSAtIDEsCiMgICAgIGogPSB0cmlbLDJdIC0gMSwKIyAgICAgayA9IHRyaVssM10gLSAxLAojICAgICB0eXBlID0gIm1lc2gzZCIsCiMgICAgIGludGVuc2l0eSA9IGZ2YWxzLAojICAgICBjb2xvcnNjYWxlID0gY29sb3JzY2FsZSwKIyAgICAgb3BhY2l0eSA9IG9wYWNpdHksCiMgICAgIGZsYXRzaGFkaW5nID0gVFJVRSwKIyAgICAgdGV4dCA9IHBhc3RlMCgKIyAgICAgICAieDogIiwgc3ByaW50ZigiJS4zZiIsIHgpLCAiPGJyPiIsCiMgICAgICAgInk6ICIsIHNwcmludGYoIiUuM2YiLCB5KSwgIjxicj4iLAojICAgICAgICJ6OiAiLCBzcHJpbnRmKCIlLjNmIiwgeiksICI8YnI+IiwKIyAgICAgICAiZjogIiwgc3ByaW50ZigiJS41ZiIsIGZ2YWxzKQojICAgICApLAojICAgICBob3ZlcmluZm8gPSAidGV4dCIKIyAgICkgJT4lIGxheW91dCgKIyAgICAgc2NlbmUgPSBnbG9iYWwuc2NlbmUuc2V0dGVyLnJlYy5hbmQuc3BoZXJlKHhfcmFuZ2UsIHlfcmFuZ2UsIHpfcmFuZ2UpCiMgICApCiMgICAKIyAgIHJldHVybihwKQojIH0KCgpwbG90XzNkX3NwaGVyZV9zdXJmYWNlX29uZWNvbCA8LSBmdW5jdGlvbihtZXNoLCBmdmFscykgewogIAogIGNvbG9yc2NhbGUgPSAiVmlyaWRpcyIKICBvcGFjaXR5ID0gMQogIAogIHggPC0gbWVzaCRsb2NbLCAxXQogIHkgPC0gbWVzaCRsb2NbLCAyXQogIHogPC0gbWVzaCRsb2NbLCAzXQogIHRyaSA8LSBtZXNoJGdyYXBoJHR2CiAgCiAgeF9yYW5nZSA8LSByYW5nZSh4KQogIHlfcmFuZ2UgPC0gcmFuZ2UoeSkKICB6X3JhbmdlIDwtIHJhbmdlKHopCiAgCiAgIyBTdGF0aWMgbWVzaDNkIHBsb3QKICBwIDwtIHBsb3RfbHkoCiAgICB4ID0geCwgeSA9IHksIHogPSB6LAogICAgaSA9IHRyaVssMV0gLSAxLAogICAgaiA9IHRyaVssMl0gLSAxLAogICAgayA9IHRyaVssM10gLSAxLAogICAgdHlwZSA9ICJtZXNoM2QiLAogICAgaW50ZW5zaXR5ID0gZnZhbHMsCiAgICBjb2xvcnNjYWxlID0gY29sb3JzY2FsZSwKICAgIG9wYWNpdHkgPSBvcGFjaXR5LAogICAgZmxhdHNoYWRpbmcgPSBUUlVFLAogICAgdGV4dCA9IHBhc3RlMCgKICAgICAgIng6ICIsIHNwcmludGYoIiUuM2YiLCB4KSwgIjxicj4iLAogICAgICAieTogIiwgc3ByaW50ZigiJS4zZiIsIHkpLCAiPGJyPiIsCiAgICAgICJ6OiAiLCBzcHJpbnRmKCIlLjNmIiwgeiksICI8YnI+IiwKICAgICAgImY6ICIsIHNwcmludGYoIiUuNWYiLCBmdmFscykKICAgICksCiAgICBob3ZlcmluZm8gPSAidGV4dCIsCiAgICBjb2xvcmJhciA9IGxpc3QoCiAgICAgIHRoaWNrbmVzcyA9IDE1LAogICAgICBsZW4gPSAwLjUsICAgICAgIyBmcmFjdGlvbiBvZiB0aGUgaGVpZ2h0IG9mIHRoZSBwbG90CiAgICAgIHkgPSAwLjUsICAgICAgICAjIGNlbnRlciB2ZXJ0aWNhbGx5ICgwID0gYm90dG9tLCAxID0gdG9wKQogICAgICB5YW5jaG9yID0gIm1pZGRsZSIsCiAgICAgIHRpdGxlID0gIiIKICAgICkKICApICU+JSBsYXlvdXQoCiAgICBzY2VuZSA9IGdsb2JhbC5zY2VuZS5zZXR0ZXIucmVjLmFuZC5zcGhlcmUoeF9yYW5nZSwgeV9yYW5nZSwgel9yYW5nZSkKICApCiAgCiAgcmV0dXJuKHApCn0KCgpwbG90XzNkX21lc2hfZWRnZXNfZmFjZXNfb2xkIDwtIGZ1bmN0aW9uKG1lc2gpIHsKICAKICB4IDwtIG1lc2gkbG9jWywxXQogIHkgPC0gbWVzaCRsb2NbLDJdCiAgeiA8LSBtZXNoJGxvY1ssM10KICB0cmkgPC0gbWVzaCRncmFwaCR0dgogIAogICMgLS0tLSBVbmlmb3JtIGdyYXkgY29sb3IgZm9yIGZhY2VzICh2ZXJ0ZXhjb2xvcikgLS0tLQogIGdyYXlfcmdiIDwtIHJlcChsaXN0KHJnYigxODAsIDE4MCwgMTgwLCBtYXhDb2xvclZhbHVlID0gMjU1KSksIGxlbmd0aCh4KSkKICAKICBwIDwtIHBsb3RfbHkoKSAlPiUgYWRkX3RyYWNlKAogICAgeCA9IHgsCiAgICB5ID0geSwKICAgIHogPSB6LAogICAgaSA9IHRyaVssMV0gLSAxLAogICAgaiA9IHRyaVssMl0gLSAxLAogICAgayA9IHRyaVssM10gLSAxLAogICAgdHlwZSA9ICJtZXNoM2QiLAogICAgdmVydGV4Y29sb3IgPSBncmF5X3JnYiwgICAjIDwtIHRoZSBjb3JyZWN0IHdheQogICAgZmxhdHNoYWRpbmcgPSBUUlVFLAogICAgb3BhY2l0eSA9IDEsCiAgICBzaG93c2NhbGUgPSBGQUxTRQogICkKICAKICAjIC0tLS0gRXh0cmFjdCB1bmlxdWUgZWRnZXMgLS0tLQogIGVkZ2VzIDwtIHJiaW5kKAogICAgdHJpWywxOjJdLAogICAgdHJpWywyOjNdLAogICAgdHJpWyxjKDMsMSldCiAgKQogIAogIGVkZ2VzIDwtIHQoYXBwbHkoZWRnZXMsIDEsIHNvcnQpKQogIGVkZ2VzIDwtIHVuaXF1ZShlZGdlcykKICAKICAjIC0tLS0gQWRkIGVkZ2VzIGFzIGJsdWUgbGluZXMgLS0tLQogIGZvciAoZSBpbiAxOm5yb3coZWRnZXMpKSB7CiAgICBpMSA8LSBlZGdlc1tlLDFdCiAgICBpMiA8LSBlZGdlc1tlLDJdCiAgICAKICAgIHAgPC0gcCAlPiUgYWRkX3RyYWNlKAogICAgICB4ID0gYyh4W2kxXSwgeFtpMl0pLAogICAgICB5ID0gYyh5W2kxXSwgeVtpMl0pLAogICAgICB6ID0gYyh6W2kxXSwgeltpMl0pLAogICAgICB0eXBlID0gInNjYXR0ZXIzZCIsCiAgICAgIG1vZGUgPSAibGluZXMiLAogICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibHVlIiwgd2lkdGggPSAzKSwKICAgICAgaG92ZXJpbmZvID0gIm5vbmUiLAogICAgICBzaG93bGVnZW5kID0gRkFMU0UKICAgICkKICB9CiAgCiAgcmV0dXJuKHApCn0KCnBsb3RfM2RfbWVzaF9lZGdlc19mYWNlcyA8LSBmdW5jdGlvbihtZXNoKSB7CiAgCiAgeCA8LSBtZXNoJGxvY1ssMV0KICB5IDwtIG1lc2gkbG9jWywyXQogIHogPC0gbWVzaCRsb2NbLDNdCiAgdHJpIDwtIG1lc2gkZ3JhcGgkdHYKICAKICB4X3JhbmdlIDwtIHJhbmdlKHgpCiAgeV9yYW5nZSA8LSByYW5nZSh5KQogIHpfcmFuZ2UgPC0gcmFuZ2UoeikKICAKICAjIC0tLS0gVW5pZm9ybSBncmF5IGNvbG9yIGZvciBmYWNlcyAtLS0tCiAgZ3JheV9yZ2IgPC0gcmVwKGxpc3QocmdiKDE4MCwgMTgwLCAxODAsIG1heENvbG9yVmFsdWUgPSAyNTUpKSwgbGVuZ3RoKHgpKQogIHdoaXRlX3JnYiA8LSByZXAobGlzdChyZ2IoMjU1LCAyNTUsIDI1NSwgYWxwaGEgPSAyNTUsIG1heENvbG9yVmFsdWUgPSAyNTUpKSwgbGVuZ3RoKHgpKQogIAogIGRmMyA8LSBkYXRhLmZyYW1lKHggPSB4LCAKICAgICAgICAgICAgICAgICAgeSA9IHksIAogICAgICAgICAgICAgICAgICB6ID0geikKICAKICAjIC0tLS0gUGxvdCBtZXNoIGZhY2VzIC0tLS0KICBwIDwtIHBsb3RfbHkoKSAlPiUgYWRkX3RyYWNlKAogICAgeCA9IHgsCiAgICB5ID0geSwKICAgIHogPSB6LAogICAgaSA9IHRyaVssMV0gLSAxLAogICAgaiA9IHRyaVssMl0gLSAxLAogICAgayA9IHRyaVssM10gLSAxLAogICAgdHlwZSA9ICJtZXNoM2QiLAogICAgdmVydGV4Y29sb3IgPSB3aGl0ZV9yZ2IsCiAgICBmbGF0c2hhZGluZyA9IFRSVUUsCiAgICBvcGFjaXR5ID0gMSwKICAgIHNob3dzY2FsZSA9IEZBTFNFCiAgKQogIAogICMgLS0tLSBFeHRyYWN0IHVuaXF1ZSBlZGdlcyAtLS0tCiAgZWRnZXMgPC0gcmJpbmQoCiAgICB0cmlbLDE6Ml0sCiAgICB0cmlbLDI6M10sCiAgICB0cmlbLGMoMywxKV0KICApCiAgZWRnZXMgPC0gdChhcHBseShlZGdlcywgMSwgc29ydCkpCiAgZWRnZXMgPC0gdW5pcXVlKGVkZ2VzKQogIAogICMgLS0tLSBFZmZpY2llbnQgZWRnZXMgYXMgYSBzaW5nbGUgdHJhY2UgdXNpbmcgTkEgYnJlYWtzIC0tLS0KICB4X2VkZ2VzIDwtIGFzLnZlY3Rvcih0KGNiaW5kKHhbZWRnZXNbLDFdXSwgeFtlZGdlc1ssMl1dLCBOQSkpKQogIHlfZWRnZXMgPC0gYXMudmVjdG9yKHQoY2JpbmQoeVtlZGdlc1ssMV1dLCB5W2VkZ2VzWywyXV0sIE5BKSkpCiAgel9lZGdlcyA8LSBhcy52ZWN0b3IodChjYmluZCh6W2VkZ2VzWywxXV0sIHpbZWRnZXNbLDJdXSwgTkEpKSkKICAKICBwIDwtIGFkZF90cmFjZSgKICAgIHAsCiAgICB4ID0geF9lZGdlcywgeSA9IHlfZWRnZXMsIHogPSB6X2VkZ2VzLAogICAgdHlwZSA9ICJzY2F0dGVyM2QiLAogICAgbW9kZSA9ICJsaW5lcyIsCiAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibGFjayIsIHdpZHRoID0gMyksCiAgICBzaG93bGVnZW5kID0gRkFMU0UsCiAgICBob3ZlcmluZm8gPSAibm9uZSIKICApICU+JSAKICAgIGFkZF90cmFjZShkYXRhID0gZGYzLCB4ID0gfngsIHkgPSB+eSwgeiA9IH56LCBtb2RlID0gIm1hcmtlcnMiLCB0eXBlID0gInNjYXR0ZXIzZCIsIAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KHNpemUgPSA0LCBjb2xvciA9ICJncmF5Iiwgc3ltYm9sID0gMTA0KSkgJT4lCiAgICBsYXlvdXQoCiAgICBzY2VuZSA9IGdsb2JhbC5zY2VuZS5zZXR0ZXIucmVjLmFuZC5zcGhlcmUoeF9yYW5nZSwgeV9yYW5nZSwgel9yYW5nZSkKICApCiAgCiAgcmV0dXJuKHApCn0KCnBsb3RfM2RfbWVzaF9lZGdlc19mYWNlc193aXRoX2hhdCA8LSBmdW5jdGlvbihtZXNoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGF0X25vZGVzID0gTlVMTCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhhdF9oZWlnaHQgPSAwLjEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoYXRfY29sb3IgPSBOVUxMLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGF0X2FscGhhID0gMC42KSB7CiAgCiAgeCA8LSBtZXNoJGxvY1ssMV0KICB5IDwtIG1lc2gkbG9jWywyXQogIHogPC0gbWVzaCRsb2NbLDNdCiAgbiA8LSBsZW5ndGgoeCkKICB0cmkgPC0gbWVzaCRncmFwaCR0dgogIAogIHhfcmFuZ2UgPC0gcmFuZ2UoeCkqMgogIHlfcmFuZ2UgPC0gcmFuZ2UoeSkqMgogIHpfcmFuZ2UgPC0gcmFuZ2UoeikqMgogIAogICMgLS0tLSBFeHRyYWN0IHVuaXF1ZSBlZGdlcyBvbmNlIC0tLS0KICBlZGdlcyA8LSByYmluZCh0cmlbLDE6Ml0sIHRyaVssMjozXSwgdHJpWyxjKDMsMSldKQogIGVkZ2VzIDwtIHQoYXBwbHkoZWRnZXMsIDEsIHNvcnQpKQogIGVkZ2VzIDwtIHVuaXF1ZShlZGdlcykKICAKICAjIEJhc2UgbWVzaCBjb2xvcnMKICB3aGl0ZV9yZ2IgPC0gcmVwKGxpc3QocmdiKDI1NSwgMjU1LCAyNTUsIGFscGhhID0gMjU1LCBtYXhDb2xvclZhbHVlID0gMjU1KSksIG4pCiAgCiAgZGYzIDwtIGRhdGEuZnJhbWUoeCA9IHgsIHkgPSB5LCB6ID0geikKICAKICAjIC0tLS0gQmFzZSBzcGhlcmljYWwgbWVzaCAtLS0tCiAgcCA8LSBwbG90X2x5KCkgJT4lIGFkZF90cmFjZSgKICAgIHggPSB4LCB5ID0geSwgeiA9IHosCiAgICBpID0gdHJpWywxXSAtIDEsIGogPSB0cmlbLDJdIC0gMSwgayA9IHRyaVssM10gLSAxLAogICAgdHlwZSA9ICJtZXNoM2QiLAogICAgdmVydGV4Y29sb3IgPSB3aGl0ZV9yZ2IsCiAgICBmbGF0c2hhZGluZyA9IFRSVUUsCiAgICBvcGFjaXR5ID0gMSwKICAgIHNob3dzY2FsZSA9IEZBTFNFLAogICAgbmFtZSA9ICJTcGhlcmUgbWVzaCIKICApCiAgCiAgIyAtLS0tIEJhc2UgbWVzaCBlZGdlcyAoc2luZ2xlIGVmZmljaWVudCB0cmFjZSkgLS0tLQogIHhfZWRnZXMgPC0gYXMudmVjdG9yKHQoY2JpbmQoeFtlZGdlc1ssMV1dLCB4W2VkZ2VzWywyXV0sIE5BKSkpCiAgeV9lZGdlcyA8LSBhcy52ZWN0b3IodChjYmluZCh5W2VkZ2VzWywxXV0sIHlbZWRnZXNbLDJdXSwgTkEpKSkKICB6X2VkZ2VzIDwtIGFzLnZlY3Rvcih0KGNiaW5kKHpbZWRnZXNbLDFdXSwgeltlZGdlc1ssMl1dLCBOQSkpKQogIAogCiAgCiAgIyAtLS0tIEhhdCBmdW5jdGlvbnMgb24gc3BoZXJlIC0tLS0KICBpZiAoIWlzLm51bGwoaGF0X25vZGVzKSkgewogICAgCiAgICAjIFZhbGlkYXRlIGluZGljZXMKICAgIGlmIChhbnkoaGF0X25vZGVzIDwgMSB8IGhhdF9ub2RlcyA+IG4pKSB7CiAgICAgIHN0b3AoImhhdF9ub2RlcyBjb250YWlucyBpbnZhbGlkIG5vZGUgaW5kaWNlcyIpCiAgICB9CiAgICAKICAgICMgQ29sb3IgcmVjeWNsaW5nCiAgICBpZiAoaXMubnVsbChoYXRfY29sb3IpKSB7CiAgICAgIGhhdF9jb2xvciA8LSBnckRldmljZXM6OnJhaW5ib3cobGVuZ3RoKGhhdF9ub2RlcykpCiAgICB9CiAgICBoYXRfY29sb3IgPC0gcmVwKGhhdF9jb2xvciwgbGVuZ3RoLm91dCA9IGxlbmd0aChoYXRfbm9kZXMpKQogICAgCiAgICBmb3IgKGsgaW4gc2VxX2Fsb25nKGhhdF9ub2RlcykpIHsKICAgICAgaTAgPC0gaGF0X25vZGVzW2tdCiAgICAgIAogICAgICAjIE5vZGFsIGhhdCBiYXNpcwogICAgICBwaGkgPC0gcmVwKDAsIG4pCiAgICAgIHBoaVtpMF0gPC0gMQogICAgICAKICAgICAgIyBMaWZ0IHJhZGlhbGx5IG91dHdhcmQgKGltcG9ydGFudCBmb3Igc3BoZXJlISkKICAgICAgciA8LSBzcXJ0KHheMiArIHleMiArIHpeMikKICAgICAgbnggPC0geCAvIHIKICAgICAgbnkgPC0geSAvIHIKICAgICAgbnogPC0geiAvIHIKICAgICAgCiAgICAgIHhfaGF0IDwtIHggKyBoYXRfaGVpZ2h0ICogcGhpICogbngKICAgICAgeV9oYXQgPC0geSArIGhhdF9oZWlnaHQgKiBwaGkgKiBueQogICAgICB6X2hhdCA8LSB6ICsgaGF0X2hlaWdodCAqIHBoaSAqIG56CiAgICAgIAogICAgICAjIEhhdCBzdXJmYWNlIGNvbG9yCiAgICAgIGhhdF9yZ2IgPC0gcmVwKGxpc3QoYWRqdXN0Y29sb3IoaGF0X2NvbG9yW2tdLCBhbHBoYS5mID0gaGF0X2FscGhhKSksIG4pCiAgICAgIAogICAgICAjIC0tLS0gSGF0IHN1cmZhY2UgLS0tLQogICAgICBwIDwtIGFkZF90cmFjZShwLAogICAgICAgIHggPSB4X2hhdCwgeSA9IHlfaGF0LCB6ID0gel9oYXQsCiAgICAgICAgaSA9IHRyaVssMV0gLSAxLCBqID0gdHJpWywyXSAtIDEsIGsgPSB0cmlbLDNdIC0gMSwKICAgICAgICB0eXBlID0gIm1lc2gzZCIsCiAgICAgICAgdmVydGV4Y29sb3IgPSBoYXRfcmdiLAogICAgICAgIGZsYXRzaGFkaW5nID0gVFJVRSwKICAgICAgICBvcGFjaXR5ID0gMSwKICAgICAgICBuYW1lID0gcGFzdGUwKCJwaGlfIiwgaTApLAogICAgICAgIHNob3dsZWdlbmQgPSBGQUxTRQogICAgICApCiAgICAgIAogICAgICAjIC0tLS0gSGF0IHdpcmVmcmFtZSBlZGdlcyAtLS0tCiAgICAgIHhfaGF0X2VkZ2VzIDwtIGFzLnZlY3Rvcih0KGNiaW5kKHhfaGF0W2VkZ2VzWywxXV0sIHhfaGF0W2VkZ2VzWywyXV0sIE5BKSkpCiAgICAgIHlfaGF0X2VkZ2VzIDwtIGFzLnZlY3Rvcih0KGNiaW5kKHlfaGF0W2VkZ2VzWywxXV0sIHlfaGF0W2VkZ2VzWywyXV0sIE5BKSkpCiAgICAgIHpfaGF0X2VkZ2VzIDwtIGFzLnZlY3Rvcih0KGNiaW5kKHpfaGF0W2VkZ2VzWywxXV0sIHpfaGF0W2VkZ2VzWywyXV0sIE5BKSkpCiAgICAgIAogICAgICBwIDwtIGFkZF90cmFjZShwLAogICAgICAgIHggPSB4X2hhdF9lZGdlcywgeSA9IHlfaGF0X2VkZ2VzLCB6ID0gel9oYXRfZWRnZXMsCiAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9IGhhdF9jb2xvcltrXSwgd2lkdGggPSAyKSwKICAgICAgICBzaG93bGVnZW5kID0gRkFMU0UsCiAgICAgICAgaG92ZXJpbmZvID0gIm5vbmUiCiAgICAgICkKICAgIH0KICB9CiAgCiAgIHAgPC0gYWRkX3RyYWNlKHAsCiAgICB4ID0geF9lZGdlcywgeSA9IHlfZWRnZXMsIHogPSB6X2VkZ2VzLAogICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsYWNrIiwgd2lkdGggPSAzKSwKICAgIHNob3dsZWdlbmQgPSBGQUxTRSwKICAgIGhvdmVyaW5mbyA9ICJub25lIgogICkKICAgCiAgIyAtLS0tIE5vZGVzIC0tLS0KICBwIDwtIGFkZF90cmFjZShwLAogICAgZGF0YSA9IGRmMywKICAgIHggPSB+eCwgeSA9IH55LCB6ID0gfnosCiAgICBtb2RlID0gIm1hcmtlcnMiLAogICAgdHlwZSA9ICJzY2F0dGVyM2QiLAogICAgbWFya2VyID0gbGlzdChzaXplID0gNCwgY29sb3IgPSAiZ3JheSIsIHN5bWJvbCA9IDEwNCksCiAgICBzaG93bGVnZW5kID0gRkFMU0UKICApCiAgCiAgIyAtLS0tIFNjZW5lIC0tLS0KICBwIDwtIHAgJT4lIGxheW91dCgKICAgIHNjZW5lID0gZ2xvYmFsLnNjZW5lLnNldHRlci5yZWMuYW5kLnNwaGVyZSh4X3JhbmdlLCB5X3JhbmdlLCB6X3JhbmdlKQogICkKICAKICByZXR1cm4ocCkKfQoKYGBgCgoKYGBge3J9CmdyYXBoLnBsb3R0ZXIuM2Qub25lY29sIDwtIGZ1bmN0aW9uKGdyYXBoLCB2ZWMpIHsKICAKICAjIENvb3JkaW5hdGVzIG9uIHRoZSBtZXNoCiAgeCA8LSBwbG90dGluZy5vcmRlcihncmFwaCRtZXNoJFZbLCAxXSwgZ3JhcGgpCiAgeSA8LSBwbG90dGluZy5vcmRlcihncmFwaCRtZXNoJFZbLCAyXSwgZ3JhcGgpCiAgeiA8LSBwbG90dGluZy5vcmRlcih2ZWMsIGdyYXBoKSAgIyB2ZWN0b3IgdG8gcGxvdAogIAogICMgQXhpcyByYW5nZXMKICB4X3JhbmdlIDwtIHJhbmdlKHgpCiAgeV9yYW5nZSA8LSByYW5nZSh5KQogIHpfcmFuZ2UgPC0gcmFuZ2UoeikKICB6X3JhbmdlWzFdIDwtIHpfcmFuZ2VbMV0gLSAxMF4tNAogIAogICMgVmVydGljYWwgbGluZXMgKHZlY3Rvcml6ZWQpCiAgbiA8LSBsZW5ndGgoeCkKICB4X3ZlcnQgPC0gcmVwKHgsIGVhY2ggPSAzKQogIHlfdmVydCA8LSByZXAoeSwgZWFjaCA9IDMpCiAgel92ZXJ0IDwtIGFzLnZlY3Rvcih0KGNiaW5kKDAsIHosIE5BKSkpCiAgCiAgIyBDcmVhdGUgcGxvdAogIHAgPC0gcGxvdF9seSgpICU+JQogICAgCiAgICAjIE1haW4gM0QgY3VydmUgb3ZlciB0aGUgZ3JhcGgKICAgIGFkZF90cmFjZSgKICAgICAgeCA9IHgsIHkgPSB5LCB6ID0geiwKICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIHdpZHRoID0gMyksCiAgICAgIHNob3dsZWdlbmQgPSBGQUxTRQogICAgKSAlPiUKICAgICMgR3JhcGggYmFzZQogICAgYWRkX3RyYWNlKAogICAgICB4ID0geCwgeSA9IHksIHogPSB6KjAsCiAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsYWNrIiwgd2lkdGggPSAzKSwKICAgICAgc2hvd2xlZ2VuZCA9IEZBTFNFCiAgICApICU+JQogICAgCiAgICAjIFZlcnRpY2FsIGxpbmVzIGZyb20gYmFzZSB0byBjdXJ2ZQogICAgYWRkX3RyYWNlKAogICAgICB4ID0geF92ZXJ0LCB5ID0geV92ZXJ0LCB6ID0gel92ZXJ0LAogICAgICB0eXBlID0gInNjYXR0ZXIzZCIsIG1vZGUgPSAibGluZXMiLAogICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJncmF5Iiwgd2lkdGggPSAwLjUpLAogICAgICBzaG93bGVnZW5kID0gRkFMU0UKICAgICkgJT4lCiAgICAKICAgIGxheW91dCgKICAgICAgc2NlbmUgPSBsaXN0KHhheGlzID0gbGlzdCh0aXRsZSA9ICJ4IiwgcmFuZ2UgPSB4X3JhbmdlKSwKICAgICAgICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAieSIsIHJhbmdlID0geV9yYW5nZSksCiAgICAgICAgICAgICAgemF4aXMgPSBsaXN0KAogICAgICB0aXRsZSA9ICJ6IiwKICAgICAgcmFuZ2UgPSB6X3JhbmdlLAogICAgICBleHBvbmVudGZvcm1hdCA9ICJlIiwKICAgICAgdGlja2Zvcm1hdCA9IE5VTEwjIi40ZiIgICAgICMgcHJldmVudHMgU0kgcHJlZml4ZXMgbGlrZSDOvCwgbSwgawogICAgKSwKICAgICAgICAgICAgICBhc3BlY3RyYXRpbyA9IGxpc3QoeCA9IDIqKDErMi9waSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gMiooMi9waSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB6ID0gMSooMi9waSkpLAogICAgICAgICAgICAgIGNhbWVyYSA9IGxpc3QoZXllID0gbGlzdCh4ID0gNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeiA9IDMuNSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZW50ZXIgPSBsaXN0KHggPSAoMSsyL3BpKS8yLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IDAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB6ID0gMCkpKQogICAgKQogIAogIHJldHVybihwKQp9CgpgYGAKCiMjIFJlZmVyZW5jZXMKCmBgYHtyLCBwdXJsID0gRkFMU0V9CmdyYXRlZnVsOjpjaXRlX3BhY2thZ2VzKG91dHB1dCA9ICJwYXJhZ3JhcGgiLCBvdXQuZGlyID0gIi4iKQpgYGAKCgo=