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\). Let \(U^0_h = P_hu_0\). For \(k=0,\dots, N-1\), \(U_h^{k+1}\in V_h\) solves the following scheme \[\begin{align} \label{system:fully_discrete_scheme} \tag{3} \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, \end{align}\] where \(f^{k+1} = \displaystyle\dfrac{1}{\tau}\int_{t_k}^{t^{k+1}}f(t)dt\). 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 linear system \[\begin{align*} \sum_{j=1}^{N_h}u_j^{k+1}[(\psi_j,\psi_i)_{L_2(\Gamma)}+ \tau\mathfrak{a}(\psi_j,\psi_i)] = \sum_{j=1}^{N_h}u_j^{k}(\psi_j,\psi_i)_{L_2(\Gamma)}+\tau( f^{k+1},\psi_i)_{L_2(\Gamma)}, \end{align*}\] for \(i = 1,\dots, N_h\). In matrix notation, \[\begin{align} \label{diff_eq_discrete} (\boldsymbol{C}+\tau \boldsymbol{L}^{\alpha/2})\boldsymbol{U}^{k+1} = \boldsymbol{C}\boldsymbol{U}^k+\tau \boldsymbol{F}^{k+1}, \end{align}\] or by introducing the scaling parameter \(\kappa^2>0\), \[\begin{align} (\boldsymbol{C}+\tau (\kappa^2)^{\alpha/2}(\boldsymbol{L}/\kappa^2)^{\alpha/2})\boldsymbol{U}^{k+1} = \boldsymbol{C}\boldsymbol{U}^k+\tau \boldsymbol{F}^{k+1}, \end{align}\] where \(\boldsymbol{C}\) has entries \(\boldsymbol{C}_{ij} = (\psi_j,\psi_i)_{L_2(\Gamma)}\), \(\boldsymbol{L}^{\alpha/2}\) has entries \(\mathfrak{a}(\psi_j,\psi_i)\), \(\boldsymbol{U}^k\) has entries \(u_j^k\), and \(\boldsymbol{F}^k\) has entries \(( f^{k},\psi_i)_{L_2(\Gamma)}\). To reduce computational cost and promote sparsity, we replace the mass matrix \(\boldsymbol{C}\) with a lumped mass matrix \(\tilde{\boldsymbol{C}}\), which is diagonal with entries \(\tilde{\boldsymbol{C}}_{ii}=\sum_{j=1}^{N_h}\boldsymbol{C}_{ij}\). For convenience, we write \(\boldsymbol{C}\) instead of \(\tilde{\boldsymbol{C}}\) in the following. Applying \((\boldsymbol{L}/\kappa^2)^{-\alpha/2}\) to both sides yields \[\begin{equation} ((\boldsymbol{L}/\kappa^2)^{-\alpha/2}\boldsymbol{C}+\tau (\kappa^2)^{\alpha/2}\boldsymbol{I})\boldsymbol{U}^{k+1} = (\boldsymbol{L}/\kappa^2)^{-\alpha/2}(\boldsymbol{C}\boldsymbol{U}^k+\tau \boldsymbol{F}^{k+1}). \end{equation}\] Following Bolin and Kirchner (2020), we approximate \((\boldsymbol{L}/\kappa^2)^{-\alpha/2}\) by \(\boldsymbol{P}_\ell^{-\top}\boldsymbol{P}_r^\top\) to arrive at \[\begin{equation} \label{eq:scheme2} \tag{5} (\boldsymbol{P}_\ell^{-\top}\boldsymbol{P}_r^\top \boldsymbol{C}+\tau(\kappa^2)^{\alpha/2} \boldsymbol{I})\boldsymbol{U}^{k+1} = \boldsymbol{P}_\ell^{-\top}\boldsymbol{P}_r^\top(\boldsymbol{C}\boldsymbol{U}^k+\tau \boldsymbol{F}^{k+1}). \end{equation}\] where \[\begin{equation} \label{eq:PLPR} \tag{6} \boldsymbol{P}_r = \prod_{i=1}^m \left(\boldsymbol{I}-r_{1i}\dfrac{\boldsymbol{C}^{-1}\boldsymbol{L}}{\kappa^2}\right)\quad\text{and}\quad \boldsymbol{P}_\ell = \dfrac{1}{\texttt{factor}}\boldsymbol{C}\prod_{j=1}^{m+1} \left(\boldsymbol{I}-r_{2j}\dfrac{\boldsymbol{C}^{-1}\boldsymbol{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 \(\hat{r}_m =q_1/q_2\) of the function \(\hat{f}(x) := 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 \(\boldsymbol{P}_r\) and \(\boldsymbol{P}_\ell\) as \[\begin{equation} \label{eq:PLPRbolin} \tag{7} \boldsymbol{P}_r = \prod_{i=1}^m \left(\boldsymbol{I}-r_{1i}\dfrac{\boldsymbol{C}^{-1}\boldsymbol{L}}{\kappa^2}\right)\quad\text{and}\quad \boldsymbol{P}_\ell = \dfrac{\kappa^{2\beta}}{\texttt{factor}}\boldsymbol{C}\prod_{j=1}^{m+1} \left(\boldsymbol{I}-r_{2j}\dfrac{\boldsymbol{C}^{-1}\boldsymbol{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 \(\boldsymbol{P}_\ell\), a convention we adopt in the following. With this under consideration, we can rewrite \(\eqref{eq:scheme2}\) as \[\begin{equation} \tag{8} (\boldsymbol{P}_r^\top \boldsymbol{C}+\tau \boldsymbol{P}_\ell^\top)\boldsymbol{U}^{k+1} = \boldsymbol{P}_r^\top(\boldsymbol{C}\boldsymbol{U}^k+\tau \boldsymbol{F}^{k+1}), \label{eq:scheme} \end{equation}\] where
\[\begin{equation} \boldsymbol{P}_r^\top = \prod_{i=1}^m \left(\boldsymbol{I}-r_{1i}\dfrac{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}\right)\quad\text{and}\quad \boldsymbol{P}_\ell^\top = \dfrac{\kappa^{2\beta}}{\texttt{factor}}\prod_{j=1}^{m+1} \left(\boldsymbol{I}-r_{2j}\dfrac{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}\right)\cdot \boldsymbol{C} \end{equation}\] since \(\boldsymbol{L}\) and \(\boldsymbol{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(\boldsymbol{I}-r_{1i}\dfrac{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}\right)+\dfrac{\tau \kappa^{2\beta}}{\texttt{factor}}\prod_{j=1}^{m+1} \left(\boldsymbol{I}-r_{2j}\dfrac{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}\right)\right)\boldsymbol{C}\boldsymbol{U}^{k+1} = \prod_{i=1}^m \left(\boldsymbol{I}-r_{1i}\dfrac{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}\right)\cdot (\boldsymbol{C}\boldsymbol{U}^k+\tau \boldsymbol{F}^{k+1}), \end{equation}\] that is, \[\begin{equation} \label{eq:final_scheme} \tag{9} \boldsymbol{U}^{k+1} = \boldsymbol{C}^{-1}\left(\prod_{i=1}^m \left(\boldsymbol{I}-r_{1i}\dfrac{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}\right)+\dfrac{\tau \kappa^{2\beta}}{\texttt{factor}}\prod_{j=1}^{m+1} \left(\boldsymbol{I}-r_{2j}\dfrac{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}\right)\right)^{-1} \prod_{i=1}^m \left(\boldsymbol{I}-r_{1i}\dfrac{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}\right)\cdot (\boldsymbol{C}\boldsymbol{U}^k+\tau \boldsymbol{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} \boldsymbol{U}^{k+1} = \boldsymbol{C}^{-1}\left(\sum_{k=1}^{m+1} a_k\left( \dfrac{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}-p_k\boldsymbol{I}\right)^{-1} + r\boldsymbol{I}\right) (\boldsymbol{C}\boldsymbol{U}^k+\tau \boldsymbol{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{\boldsymbol{L}\boldsymbol{C}^{-1}}{\kappa^2}-p_k\boldsymbol{I}\right)^{-1} = \boldsymbol{C}\left( \dfrac{\boldsymbol{L}}{\kappa^2}-p_k\boldsymbol{C}\right)^{-1}\), we have that \(\eqref{eq:final_scheme2}\) can be rewritten as

\[\begin{equation} \label{eq:final_scheme3} \tag{12} \boldsymbol{U}^{k+1} = \left(\sum_{k=1}^{m+1} a_k\left( \dfrac{\boldsymbol{L}}{\kappa^2}-p_k\boldsymbol{C}\right)^{-1}\right) (\boldsymbol{C}\boldsymbol{U}^k+\tau \boldsymbol{F}^{k+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
                ))
  }
}

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 system \(\eqref{eq:final_scheme2}\) for the vector v. If beta = 1, it solves the linear 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
    output <- v*0
    for (i in 1:(m+1)) {output <- output + solve(partial_fraction_terms[[i]], v)}
    return(output # solve the linear system using the partial fraction decomposition
           )
  }
}

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 Functions for computing the true line rates

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)))])
}
# helper: measure change relative to the size of the previous iterate 
change_comparer <- function(X_new, X_old, time_step, time_seq, weights, relative = TRUE) {
  num <- sqrt(as.double(t(weights) %*% ((X_new - X_old)^2) %*% rep(time_step, length(time_seq))))
  if (!relative) {
    return(num)
    }
  den <- sqrt(as.double(t(weights) %*% (X_new^2) %*% rep(time_step, length(time_seq))))
  if (den < .Machine$double.eps) {
    return(ifelse(num < .Machine$double.eps, 0, num))
  } else {
    return(num / den)
  }
}
# Coupled solver with multi-criteria convergence
solve_coupled_system_multi_tol <- function(
  my_op_frac,           # operator
  time_step,            # tau
  time_seq,             # vector of times 
  u_0,                  # initial state U^0 
  F_proj,               # matrix of F 
  V_d,                  # matrix of 
  Psi,                  # Psi matrix
  R,                    # R matrix
  A, B,                 # lower/upper bounds (vector or matrix broadcastable to time grid)
  mu,                   # positive scalar
  weights,
  tol = 1e-8,           # scalar or named list: list(Z=..., U=..., P=...)
  maxit = 200,
  verbose = FALSE,
  true_sol
) {

  if (is.numeric(tol) && length(tol) == 1) {
    tol_list <- list(Z = tol, U = tol, P = tol)
  } else if (is.list(tol)) {
    tol_list <- modifyList(list(Z = 1e-8, U = 1e-8, P = 1e-8), tol)
  } else stop("tol must be scalar or list(Z=...,U=...,P=...)")

  it <- 0
  converged <- FALSE
  
  rel_history <- data.frame(iter = integer(0), variable = character(0), value = numeric(0))
  abs_history <- data.frame(iter = integer(0), variable = character(0), value = numeric(0))

  z_prev <- F_proj*100
  Z_mat <- R %*% Psi %*% z_prev
  U_prev <- F_proj*100
  P_prev <- F_proj*100

  repeat {
    it <- it + 1

    U_mat <- solve_fractional_evolution(my_op_frac, time_step, time_seq, val_at_0 = u_0, RHST = F_proj + Z_mat)
    V_mat <- reversecolumns(R %*% Psi %*% U_mat)
    Q_mat <- solve_fractional_evolution(my_op_frac, time_step, time_seq, val_at_0 = u_0 * 0, RHST = V_mat - V_d)
    P_mat <- reversecolumns(Q_mat)
    z_new <- pmax(A, pmin(B, - P_mat / mu))
    Z_mat <- R %*% Psi %*% z_new
    
    # relative changes
    rel_changes_Z <- change_comparer(z_new, z_prev, time_step, time_seq, weights, relative = TRUE)  
    rel_changes_U <- change_comparer(U_mat, U_prev, time_step, time_seq, weights, relative = TRUE)
    rel_changes_P <- change_comparer(P_mat, P_prev, time_step, time_seq, weights, relative = TRUE)
    abs_changes_Z <- change_comparer(z_new, true_sol$z_bar, time_step, time_seq, weights, relative = FALSE)
    abs_changes_U <- change_comparer(U_mat, true_sol$u_bar, time_step, time_seq, weights, relative = FALSE)
    abs_changes_P <- change_comparer(P_mat, true_sol$p_bar, time_step, time_seq, weights, relative = FALSE)

    rel_history <- rbind(rel_history,
      data.frame(iter = it, variable = "Z", value = rel_changes_Z),
      data.frame(iter = it, variable = "U", value = rel_changes_U),
      data.frame(iter = it, variable = "P", value = rel_changes_P))
    abs_history <- rbind(abs_history,
      data.frame(iter = it, variable = "Z", value = abs_changes_Z),
      data.frame(iter = it, variable = "U", value = abs_changes_U),
      data.frame(iter = it, variable = "P", value = abs_changes_P))
    
    if (verbose) {message(sprintf("iter %3d: rel(Z)=%.3e, rel(U)=%.3e, rel(P)=%.3e", it, rel_changes_Z, rel_changes_U, rel_changes_P))}

    # update stored previous iterates
    z_prev <- z_new
    U_prev <- U_mat
    P_prev <- P_mat

    # convergence check: require all rel_changes <= respective tol
    cond_Z <- rel_changes_Z <= tol_list$Z
    cond_U <- rel_changes_U <= tol_list$U
    cond_P <- rel_changes_P <= tol_list$P

    if ((cond_Z && cond_U && cond_P) || it >= maxit) {
      converged <- (cond_Z && cond_U && cond_P)
      break
    }
  }

  if (verbose && !converged) {
    message(sprintf(
      "Stopped at maxit=%d; rel_changes: Z=%.3e (tol %.3e), U=%.3e (tol %.3e), P=%.3e (tol %.3e)",
      it, rel_changes_Z, tol_list$Z, rel_changes_U, tol_list$U, rel_changes_P, tol_list$P
    ))
  }

  return(list(U = U_mat,  # solution U
              Z = z_new,  # solution z
              P = P_mat, # solution P
              iterations = it,
              converged = converged,
              tol_list = tol_list,
              rel_history = rel_history,
              abs_history = abs_history))
}

plot_convergence_history <- function(history_df, tol_list = NULL, relative = TRUE) {

  p <- ggplot(history_df, aes(x = iter, y = value, color = variable)) +
    geom_line() +
    geom_point(size = 1.5) +
    scale_y_log10() +
    labs(
      title = ifelse(relative, "|X_{iter} - X_{iter-1}| / |X_{iter}|", "|X_{exact} - X_{iter}|"),
      x = "Iteration",
      y = "Error",
      color = "Quantity"
    ) +
    theme_minimal()
  
  # Add tolerance lines if provided
  if (!is.null(tol_list)) {
    tol_df <- data.frame(
      variable = names(tol_list),
      tol = unlist(tol_list)
    )
    p <- p + geom_hline(
      data = tol_df,
      aes(yintercept = tol, color = variable),
      linetype = "dashed"
    )
  }
  
  return(plotly::ggplotly(p))
}

5 Plotting functions

5.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]))
}

5.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))))
}

5.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 <- 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 <- 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)
}

5.4 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)
}

5.5 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)
}

5.6 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)
}

5.7 Function error.convergence.plotter()

# 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)
}
graph.plotter.3d.static <- function(graph, ...) {
  x <- plotting.order(graph$mesh$V[, 1], graph)
  y <- plotting.order(graph$mesh$V[, 2], graph)

  z_list <- list(...)
  z_list <- lapply(z_list, function(z) plotting.order(z, graph))
  U_names <- sapply(substitute(list(...))[-1], deparse)

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

  p <- plot_ly()
  colors <- RColorBrewer::brewer.pal(max(length(z_list), 3), "Set1")

  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)
}

6 References

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

We used R version 4.5.0 (R Core Team 2025) and the following R packages: 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), knitr v. 1.50 (Xie 2014, 2015, 2025), 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), patchwork v. 1.3.1 (Pedersen 2025), plotly v. 4.10.4 (Sievert 2020), RColorBrewer v. 1.1.3 (Neuwirth 2022), renv v. 1.0.7 (Ushey and Wickham 2024), reshape2 v. 1.4.4 (Wickham 2007), rmarkdown v. 2.29 (Xie, Allaire, and Grolemund 2018; Xie, Dervieux, and Riederer 2020; Allaire et al. 2024), rSPDE v. 2.5.1.9000 (Bolin and Kirchner 2020; Bolin and Simas 2023; Bolin, Simas, and Xiong 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), xaringanExtra v. 0.8.0 (Aden-Buie and Warkentin 2024).

Aden-Buie, Garrick, and Matthew T. Warkentin. 2024. xaringanExtra: Extras and Extensions for xaringan Slides. https://doi.org/10.32614/CRAN.package.xaringanExtra.
Allaire, JJ, Yihui Xie, Christophe Dervieux, Jonathan McPherson, Javier Luraschi, Kevin Ushey, Aron Atkins, et al. 2024. rmarkdown: Dynamic Documents for r. https://github.com/rstudio/rmarkdown.
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.
Cheng, Joe, Carson Sievert, Barret Schloerke, Winston Chang, Yihui Xie, and Jeff Allen. 2024. htmltools: Tools for HTML. https://doi.org/10.32614/CRAN.package.htmltools.
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.
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. https://doi.org/10.32614/CRAN.package.RColorBrewer.
Pedersen, Thomas Lin. 2025. patchwork: The Composer of Plots. https://doi.org/10.32614/CRAN.package.patchwork.
R Core Team. 2025. R: A Language and Environment for Statistical Computing. Vienna, Austria: R Foundation for Statistical Computing. https://www.R-project.org/.
Sievert, Carson. 2020. Interactive Web-Based Data Visualization with r, Plotly, and Shiny. Chapman; Hall/CRC. https://plotly-r.com.
Ushey, Kevin, and Hadley Wickham. 2024. renv: Project Environments. https://doi.org/10.32614/CRAN.package.renv.
Van Boxtel, G.J.M., et al. 2021. gsignal: Signal Processing. https://github.com/gjmvanboxtel/gsignal.
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://doi.org/10.32614/CRAN.package.scales.
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/.
———. 2025. knitr: A General-Purpose Package for Dynamic Report Generation in R. https://yihui.org/knitr/.
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.
LS0tCnRpdGxlOiAiRnVuY3Rpb25hbGl0eSIKZGF0ZTogIkxhc3QgbW9kaWZpZWQ6IGByIGZvcm1hdChTeXMudGltZSgpLCAnJWQtJW0tJVkuJylgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIG1hdGhqYXg6ICJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL21hdGhqYXhAMy9lczUvdGV4LW1tbC1jaHRtbC5qcyIKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRoZW1lOiBmbGF0bHkKICAgIGNvZGVfZm9sZGluZzogc2hvdyAjIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUiIHRvIGhpZGUgY29kZSBhbmQgYWRkIGEgYnV0dG9uIHRvIHNob3cgaXQKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGZpZ19jYXB0aW9uOiB0cnVlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjc3M6IHZpc3VhbC5jc3MKYWx3YXlzX2FsbG93X2h0bWw6IHRydWUKYmlibGlvZ3JhcGh5OiAKICAtIHJlZmVyZW5jZXMuYmliCiAgLSBncmF0ZWZ1bC1yZWZzLmJpYgpoZWFkZXItaW5jbHVkZXM6CiAgLSBcbmV3Y29tbWFuZHtcYXJ9e1xtYXRoYmJ7Un19CiAgLSBcbmV3Y29tbWFuZHtcbGxhdn1bMV17XGxlZnRceyMxXHJpZ2h0XH19CiAgLSBcbmV3Y29tbWFuZHtccGFyZX1bMV17XGxlZnQoIzFccmlnaHQpfQogIC0gXG5ld2NvbW1hbmR7XE5jYWx9e1xtYXRoY2Fse059fQogIC0gXG5ld2NvbW1hbmR7XFZjYWx9e1xtYXRoY2Fse1Z9fQogIC0gXG5ld2NvbW1hbmR7XEVjYWx9e1xtYXRoY2Fse0V9fQogIC0gXG5ld2NvbW1hbmR7XFdjYWx9e1xtYXRoY2Fse1d9fQotLS0KCkdvIGJhY2sgdG8gdGhlIFtDb250ZW50c10oYWJvdXQuaHRtbCkgcGFnZS4KCjxkaXYgc3R5bGU9ImNvbG9yOiAjMmMzZTUwOyB0ZXh0LWFsaWduOiByaWdodDsiPgoqKioqKioqKiAgCjxzdHJvbmc+UHJlc3MgU2hvdyB0byByZXZlYWwgdGhlIGNvZGUgY2h1bmtzLjwvc3Ryb25nPiAgCgoqKioqKioqKgo8L2Rpdj4KCgpgYGB7ciwgcHVybCA9IEZBTFNFLCBlY2hvID0gRkFMU0V9CiMgQ3JlYXRlIGEgY2xpcGJvYXJkIGJ1dHRvbiBvbiB0aGUgcmVuZGVyZWQgSFRNTCBwYWdlCnNvdXJjZShoZXJlOjpoZXJlKCJjbGlwYm9hcmQuUiIpKTsgY2xpcGJvYXJkCmBgYAoKCmBgYHtyLCBwdXJsID0gRkFMU0UsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQojIFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoMTk4MikgCiMgU2V0IGdsb2JhbCBvcHRpb25zIGZvciBhbGwgY29kZSBjaHVua3MKa25pdHI6Om9wdHNfY2h1bmskc2V0KAogICMgRGlzYWJsZSBtZXNzYWdlcyBwcmludGVkIGJ5IFIgY29kZSBjaHVua3MKICBtZXNzYWdlID0gRkFMU0UsICAgIAogICMgRGlzYWJsZSB3YXJuaW5ncyBwcmludGVkIGJ5IFIgY29kZSBjaHVua3MKICB3YXJuaW5nID0gRkFMU0UsICAgIAogICMgU2hvdyBSIGNvZGUgd2l0aGluIGNvZGUgY2h1bmtzIGluIG91dHB1dAogIGVjaG8gPSBUUlVFLCAgICAgICAgCiAgIyBJbmNsdWRlIGJvdGggUiBjb2RlIGFuZCBpdHMgcmVzdWx0cyBpbiBvdXRwdXQKICBpbmNsdWRlID0gVFJVRSwgICAgIAogICMgRXZhbHVhdGUgUiBjb2RlIGNodW5rcwogIGV2YWwgPSBUUlVFLCAgICAgICAKICAjIEVuYWJsZSBjYWNoaW5nIG9mIFIgY29kZSBjaHVua3MgZm9yIGZhc3RlciByZW5kZXJpbmcKICBjYWNoZSA9IEZBTFNFLCAgICAgIAogICMgQWxpZ24gZmlndXJlcyBpbiB0aGUgY2VudGVyIG9mIHRoZSBvdXRwdXQKICBmaWcuYWxpZ24gPSAiY2VudGVyIiwKICAjIEVuYWJsZSByZXRpbmEgZGlzcGxheSBmb3IgaGlnaC1yZXNvbHV0aW9uIGZpZ3VyZXMKICByZXRpbmEgPSAyLAogICMgU2hvdyBlcnJvcnMgaW4gdGhlIG91dHB1dCBpbnN0ZWFkIG9mIHN0b3BwaW5nIHJlbmRlcmluZwogIGVycm9yID0gVFJVRSwKICAjIERvIG5vdCBjb2xsYXBzZSBjb2RlIGFuZCBvdXRwdXQgaW50byBhIHNpbmdsZSBibG9jawogIGNvbGxhcHNlID0gRkFMU0UKKQojIFN0YXJ0IHRoZSBmaWd1cmUgY291bnRlcgpmaWdfY291bnQgPC0gMAojIERlZmluZSB0aGUgY2FwdGlvbmVyIGZ1bmN0aW9uCmNhcHRpb25lciA8LSBmdW5jdGlvbihjYXB0aW9uKSB7CiAgZmlnX2NvdW50IDw8LSBmaWdfY291bnQgKyAxCiAgcGFzdGUwKCJGaWd1cmUgIiwgZmlnX2NvdW50LCAiOiAiLCBjYXB0aW9uKQp9CmBgYAoKCgpgYGB7cn0KIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiZGF2aWRib2xpbi9yc3BkZSIsIHJlZiA9ICJkZXZlbCIpCiMgcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoImRhdmlkYm9saW4vbWV0cmljZ3JhcGgiLCByZWYgPSAiZGV2ZWwiKQpsaWJyYXJ5KHJTUERFKQpsaWJyYXJ5KE1ldHJpY0dyYXBoKQpsaWJyYXJ5KGdyYXRlZnVsKQoKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KHBsb3RseSkKYGBgCgoKIyMgRnJhY3Rpb25hbCBkaWZmdXNpb24gZXF1YXRpb25zIG9uIG1ldHJpYyBncmFwaHMKCldlIGFuYWx5emUgYW5kIG51bWVyaWNhbGx5IGFwcHJveGltYXRlIHNvbHV0aW9ucyB0byBmcmFjdGlvbmFsIGRpZmZ1c2lvbiBlcXVhdGlvbnMgb24gbWV0cmljIGdyYXBocyBvZiB0aGUgZm9ybQpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTptYWluZXF9Clx0YWd7MX0KXGxlZnRcewpcYmVnaW57YWxpZ25lZH0KICAgIFxwYXJ0aWFsX3QgdShzLHQpICsgKFxrYXBwYV4yIC0gXERlbHRhX1xHYW1tYSlee1xhbHBoYS8yfSB1KHMsdCkgJj0gZihzLHQpLCAmJiBccXVhZCAocyx0KSBcaW4gXEdhbW1hIFx0aW1lcyAoMCwgVCksIFxcCiAgICB1KHMsMCkgJj0gdV8wKHMpLCAmJiBccXVhZCBzIFxpbiBcR2FtbWEsClxlbmR7YWxpZ25lZH0KXHJpZ2h0LgpcZW5ke2VxdWF0aW9ufQp3aXRoICR1KFxjZG90LHQpJCBzYXRpc2Z5aW5nIHRoZSBLaXJjaGhvZmYgdmVydGV4IGNvbmRpdGlvbnMKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXE6S2NvbmR9Clx0YWd7Mn0KICAgXG1hdGhjYWx7S30gPSAgXGxlZnRce1xwaGlcaW4gQyhcR2FtbWEpXDtcbWlkZGxlfFw7IFxmb3JhbGwgdlxpbiBcbWF0aGNhbHtWfTpcOyBcc3VtX3tlXGluXG1hdGhjYWx7RX1fdn1ccGFydGlhbF9lIFxwaGkodik9MCBccmlnaHRcfS4KXGVuZHtlcXVhdGlvbn0KSGVyZSAkXEdhbW1hID0gKFxtYXRoY2Fse1Z9LFxtYXRoY2Fse0V9KSQgaXMgYSBtZXRyaWMgZ3JhcGgsICRca2FwcGE+MCQsICRcYWxwaGFcaW4oMCwyXSQgZGV0ZXJtaW5lcyB0aGUgc21vb3RobmVzcyBvZiAkdShcY2RvdCx0KSQsICRcRGVsdGFfe1xHYW1tYX0kIGlzIHRoZSBzby1jYWxsZWQgS2lyY2hob2ZmLS1MYXBsYWNpYW4sIGFuZCAkZjpcR2FtbWFcdGltZXMgKDAsVClcdG9cbWF0aGJie1J9JCBhbmQgJHVfMDogXEdhbW1hIFx0byBcbWF0aGJie1J9JCBhcmUgZml4ZWQgZnVuY3Rpb25zLCBjYWxsZWQgcmlnaHQtaGFuZCBzaWRlIGFuZCBpbml0aWFsIGNvbmRpdGlvbiwgcmVzcGVjdGl2ZWx5LgoKIyMgTnVtZXJpY2FsIFNjaGVtZSB7I251bV9zY2hlbWV9CgpMZXQgJFxhbHBoYVxpbigwLDJdJCBhbmQgJFVfaF5cdGF1JCBkZW5vdGUgdGhlIHNlcXVlbmNlIG9mIGFwcHJveGltYXRpb25zIG9mIHRoZSBzb2x1dGlvbiB0byB0aGUgd2VhayBmb3JtIG9mIHByb2JsZW0gXGVxcmVme2VxOm1haW5lcX0gYXQgZWFjaCB0aW1lIHN0ZXAgb24gYSBtZXNoIGluZGV4ZWQgYnkgJGgkLiBMZXQgJFVeMF9oID0gUF9odV8wJC4gRm9yICRrPTAsXGRvdHMsIE4tMSQsICRVX2hee2srMX1caW4gVl9oJCBzb2x2ZXMgdGhlIGZvbGxvd2luZyBzY2hlbWUKXGJlZ2lue2FsaWdufQpcbGFiZWx7c3lzdGVtOmZ1bGx5X2Rpc2NyZXRlX3NjaGVtZX0KXHRhZ3szfQogICAgICAgIFxsYW5nbGVcZGVsdGEgVV9oXntrKzF9LFxwaGlccmFuZ2xlICsgXG1hdGhmcmFre2F9KFVfaF57aysxfSxccGhpKSA9IFxsYW5nbGUgZl57aysxfSxccGhpXHJhbmdsZSAsXHF1YWRcZm9yYWxsXHBoaVxpbiBWX2gsClxlbmR7YWxpZ259CndoZXJlICRmXntrKzF9ID0gXGRpc3BsYXlzdHlsZVxkZnJhY3sxfXtcdGF1fVxpbnRfe3Rfa31ee3Ree2srMX19Zih0KWR0JC4gQXQgZWFjaCB0aW1lIHN0ZXAgJHRfayQsIHRoZSBmaW5pdGUgZWxlbWVudCBzb2x1dGlvbiAkVV9oXmtcaW4gVl9oJCB0byBcZXFyZWZ7c3lzdGVtOmZ1bGx5X2Rpc2NyZXRlX3NjaGVtZX0gY2FuIGJlIGV4cHJlc3NlZCBhcyBhIGxpbmVhciBjb21iaW5hdGlvbiBvZiB0aGUgYmFzaXMgZnVuY3Rpb25zICAkXHtccHNpXmlfaFx9X3tpPTF9XntOX2h9JCBpbnRyb2R1Y2VkIGluIHRoZSBbUHJlbGltaW5hcmllc10ocHJlbGltaW5hcmllcy5odG1sI2ZlbS1iYXNpcykgcGFnZSwgbmFtZWx5LCAKXGJlZ2lue2FsaWdufQpcbGFiZWx7bnVtX3NvbH0KXHRhZ3s0fQogICAgVV9oXmsocykgPSAgXHN1bV97aT0xfV57Tl9ofXVfaV5rXHBzaV5pX2gocyksIFw7c1xpblxHYW1tYS4KXGVuZHthbGlnbn0KUmVwbGFjaW5nIFxlcXJlZntudW1fc29sfSBpbnRvIFxlcXJlZntzeXN0ZW06ZnVsbHlfZGlzY3JldGVfc2NoZW1lfSB5aWVsZHMgdGhlIGZvbGxvd2luZyBsaW5lYXIgc3lzdGVtClxiZWdpbnthbGlnbip9CiAgICBcc3VtX3tqPTF9XntOX2h9dV9qXntrKzF9WyhccHNpX2osXHBzaV9pKV97TF8yKFxHYW1tYSl9KyBcdGF1XG1hdGhmcmFre2F9KFxwc2lfaixccHNpX2kpXSA9IFxzdW1fe2o9MX1ee05faH11X2pee2t9KFxwc2lfaixccHNpX2kpX3tMXzIoXEdhbW1hKX0rXHRhdSggZl57aysxfSxccHNpX2kpX3tMXzIoXEdhbW1hKX0sClxlbmR7YWxpZ24qfQpmb3IgJGkgPSAxLFxkb3RzLCBOX2gkLiBJbiBtYXRyaXggbm90YXRpb24sClxiZWdpbnthbGlnbn0KXGxhYmVse2RpZmZfZXFfZGlzY3JldGV9CiAgICAoXGJvbGRzeW1ib2x7Q30rXHRhdSBcYm9sZHN5bWJvbHtMfV57XGFscGhhLzJ9KVxib2xkc3ltYm9se1V9XntrKzF9ID0gXGJvbGRzeW1ib2x7Q31cYm9sZHN5bWJvbHtVfV5rK1x0YXUgXGJvbGRzeW1ib2x7Rn1ee2srMX0sClxlbmR7YWxpZ259Cm9yIGJ5IGludHJvZHVjaW5nIHRoZSBzY2FsaW5nIHBhcmFtZXRlciAkXGthcHBhXjI+MCQsClxiZWdpbnthbGlnbn0KICAgIChcYm9sZHN5bWJvbHtDfStcdGF1IChca2FwcGFeMilee1xhbHBoYS8yfShcYm9sZHN5bWJvbHtMfS9ca2FwcGFeMilee1xhbHBoYS8yfSlcYm9sZHN5bWJvbHtVfV57aysxfSA9IFxib2xkc3ltYm9se0N9XGJvbGRzeW1ib2x7VX1eaytcdGF1IFxib2xkc3ltYm9se0Z9XntrKzF9LApcZW5ke2FsaWdufQp3aGVyZSAkXGJvbGRzeW1ib2x7Q30kIGhhcyBlbnRyaWVzICRcYm9sZHN5bWJvbHtDfV97aWp9ID0gKFxwc2lfaixccHNpX2kpX3tMXzIoXEdhbW1hKX0kLCAkXGJvbGRzeW1ib2x7TH1ee1xhbHBoYS8yfSQgaGFzIGVudHJpZXMgJFxtYXRoZnJha3thfShccHNpX2osXHBzaV9pKSQsICRcYm9sZHN5bWJvbHtVfV5rJCBoYXMgZW50cmllcyAkdV9qXmskLCBhbmQgJFxib2xkc3ltYm9se0Z9XmskIGhhcyBlbnRyaWVzICQoIGZee2t9LFxwc2lfaSlfe0xfMihcR2FtbWEpfSQuICBUbyByZWR1Y2UgY29tcHV0YXRpb25hbCBjb3N0IGFuZCBwcm9tb3RlIHNwYXJzaXR5LCB3ZSByZXBsYWNlIHRoZSBtYXNzIG1hdHJpeCAkXGJvbGRzeW1ib2x7Q30kIHdpdGggYSBsdW1wZWQgbWFzcyBtYXRyaXggJFx0aWxkZXtcYm9sZHN5bWJvbHtDfX0kLCB3aGljaCBpcyBkaWFnb25hbCB3aXRoIGVudHJpZXMgJFx0aWxkZXtcYm9sZHN5bWJvbHtDfX1fe2lpfT1cc3VtX3tqPTF9XntOX2h9XGJvbGRzeW1ib2x7Q31fe2lqfSQuICBGb3IgY29udmVuaWVuY2UsIHdlIHdyaXRlICRcYm9sZHN5bWJvbHtDfSQgaW5zdGVhZCBvZiAkXHRpbGRle1xib2xkc3ltYm9se0N9fSQgaW4gdGhlIGZvbGxvd2luZy4gQXBwbHlpbmcgJChcYm9sZHN5bWJvbHtMfS9ca2FwcGFeMileey1cYWxwaGEvMn0kIHRvIGJvdGggc2lkZXMgeWllbGRzClxiZWdpbntlcXVhdGlvbn0KKChcYm9sZHN5bWJvbHtMfS9ca2FwcGFeMileey1cYWxwaGEvMn1cYm9sZHN5bWJvbHtDfStcdGF1IChca2FwcGFeMilee1xhbHBoYS8yfVxib2xkc3ltYm9se0l9KVxib2xkc3ltYm9se1V9XntrKzF9ID0gKFxib2xkc3ltYm9se0x9L1xrYXBwYV4yKV57LVxhbHBoYS8yfShcYm9sZHN5bWJvbHtDfVxib2xkc3ltYm9se1V9XmsrXHRhdSBcYm9sZHN5bWJvbHtGfV57aysxfSkuClxlbmR7ZXF1YXRpb259CkZvbGxvd2luZyBAclNQREUyMDIwLCB3ZSBhcHByb3hpbWF0ZSAkKFxib2xkc3ltYm9se0x9L1xrYXBwYV4yKV57LVxhbHBoYS8yfSQgYnkgJFxib2xkc3ltYm9se1B9X1xlbGxeey1cdG9wfVxib2xkc3ltYm9se1B9X3JeXHRvcCQgdG8gYXJyaXZlIGF0ClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse2VxOnNjaGVtZTJ9Clx0YWd7NX0KKFxib2xkc3ltYm9se1B9X1xlbGxeey1cdG9wfVxib2xkc3ltYm9se1B9X3JeXHRvcCBcYm9sZHN5bWJvbHtDfStcdGF1KFxrYXBwYV4yKV57XGFscGhhLzJ9IFxib2xkc3ltYm9se0l9KVxib2xkc3ltYm9se1V9XntrKzF9ID0gXGJvbGRzeW1ib2x7UH1fXGVsbF57LVx0b3B9XGJvbGRzeW1ib2x7UH1fcl5cdG9wKFxib2xkc3ltYm9se0N9XGJvbGRzeW1ib2x7VX1eaytcdGF1IFxib2xkc3ltYm9se0Z9XntrKzF9KS4KXGVuZHtlcXVhdGlvbn0Kd2hlcmUKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXE6UExQUn0KXHRhZ3s2fQpcYm9sZHN5bWJvbHtQfV9yID0gXHByb2Rfe2k9MX1ebSBcbGVmdChcYm9sZHN5bWJvbHtJfS1yX3sxaX1cZGZyYWN7XGJvbGRzeW1ib2x7Q31eey0xfVxib2xkc3ltYm9se0x9fXtca2FwcGFeMn1ccmlnaHQpXHF1YWRcdGV4dHthbmR9XHF1YWQgXGJvbGRzeW1ib2x7UH1fXGVsbCA9IFxkZnJhY3sxfXtcdGV4dHR0e2ZhY3Rvcn19XGJvbGRzeW1ib2x7Q31ccHJvZF97aj0xfV57bSsxfSBcbGVmdChcYm9sZHN5bWJvbHtJfS1yX3syan1cZGZyYWN7XGJvbGRzeW1ib2x7Q31eey0xfVxib2xkc3ltYm9se0x9fXtca2FwcGFeMn1ccmlnaHQpLApcZW5ke2VxdWF0aW9ufQphbmQgJFx0ZXh0dHR7ZmFjdG9yfSA9IFxkZnJhY3tjX219e2Jfe20rMX19JCwgYW5kCiRce3JfezFpfVx9X3tpPTF9Xm0kIGFuZCAkXHtyX3syan1cfV97aj0xfV57bSsxfSQgYXJlIHRoZSByb290cyBvZiAkcV8xKHgpID1cc3VtX3tpPTB9Xm1jX2l4XntpfSQgYW5kICAkcV8yKHgpPVxzdW1fe2o9MH1ee20rMX1iX2p4XntqfSQsIHJlc3BlY3RpdmVseS4gVGhlIGNvZWZmaWNpZW50cyAgJFx7Y19pXH1fe2k9MH1ebSQgYW5kICAkXHtiX2pcfV97aj0wfV57bSsxfSQgYXJlIGRldGVybWluZWQgYXMgdGhlIGJlc3QgcmF0aW9uYWwgYXBwcm94aW1hdGlvbiAkXGhhdHtyfV9tID1xXzEvcV8yJCBvZiB0aGUgZnVuY3Rpb24gJFxoYXR7Zn0oeCkgOj0geF57XGFscGhhLzItMX0kIG92ZXIgdGhlIGludGVydmFsICRKX2g6ID0gW1xrYXBwYV57Mn1cbGFtYmRhX3tOX2gsaH1eey0xfSwgXGthcHBhXnsyfVxsYW1iZGFfezEsaH1eey0xfV0kLCB3aGVyZSAkXGxhbWJkYV97MSxofSwgXGxhbWJkYV97Tl9oLGh9PjAkIGFyZSB0aGUgc21hbGxlc3QgYW5kIHRoZSBsYXJnZXN0IGVpZ2VudmFsdWUgb2YgJExfaCQsIHJlc3BlY3RpdmVseS4KCgpGb3IgdGhlIHNha2Ugb2YgY2xhcml0eSwgd2Ugbm90ZSB0aGF0IHRoZSBudW1lcmljYWwgaW1wbGVtZW50YXRpb24gb2YgQHJTUERFMjAyMCBhY3R1YWxseSBkZWZpbmVzICRcYm9sZHN5bWJvbHtQfV9yJCBhbmQgJFxib2xkc3ltYm9se1B9X1xlbGwkIGFzClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse2VxOlBMUFJib2xpbn0KXHRhZ3s3fQpcYm9sZHN5bWJvbHtQfV9yID0gXHByb2Rfe2k9MX1ebSBcbGVmdChcYm9sZHN5bWJvbHtJfS1yX3sxaX1cZGZyYWN7XGJvbGRzeW1ib2x7Q31eey0xfVxib2xkc3ltYm9se0x9fXtca2FwcGFeMn1ccmlnaHQpXHF1YWRcdGV4dHthbmR9XHF1YWQgXGJvbGRzeW1ib2x7UH1fXGVsbCA9IFxkZnJhY3tca2FwcGFeezJcYmV0YX19e1x0ZXh0dHR7ZmFjdG9yfX1cYm9sZHN5bWJvbHtDfVxwcm9kX3tqPTF9XnttKzF9IFxsZWZ0KFxib2xkc3ltYm9se0l9LXJfezJqfVxkZnJhY3tcYm9sZHN5bWJvbHtDfV57LTF9XGJvbGRzeW1ib2x7TH19e1xrYXBwYV4yfVxyaWdodCksClxlbmR7ZXF1YXRpb259CndoZXJlICRcYmV0YSA9IFxhbHBoYS8yJCBhbmQgdGhlIHNjYWxpbmcgZmFjdG9yICQoXGthcHBhXjIpXntcYWxwaGEvMn0kIG9yICRca2FwcGFeezJcYmV0YX0kIGlzIGFscmVhZHkgaW5jb3Jwb3JhdGVkIGluICRcYm9sZHN5bWJvbHtQfV9cZWxsJCwgYSBjb252ZW50aW9uIHdlIGFkb3B0IGluIHRoZSBmb2xsb3dpbmcuIFdpdGggdGhpcyB1bmRlciBjb25zaWRlcmF0aW9uLCB3ZSBjYW4gcmV3cml0ZSBcZXFyZWZ7ZXE6c2NoZW1lMn0gYXMKXGJlZ2lue2VxdWF0aW9ufQpcdGFnezh9CihcYm9sZHN5bWJvbHtQfV9yXlx0b3AgXGJvbGRzeW1ib2x7Q30rXHRhdSBcYm9sZHN5bWJvbHtQfV9cZWxsXlx0b3ApXGJvbGRzeW1ib2x7VX1ee2srMX0gPSBcYm9sZHN5bWJvbHtQfV9yXlx0b3AoXGJvbGRzeW1ib2x7Q31cYm9sZHN5bWJvbHtVfV5rK1x0YXUgXGJvbGRzeW1ib2x7Rn1ee2srMX0pLApcbGFiZWx7ZXE6c2NoZW1lfQpcZW5ke2VxdWF0aW9ufQp3aGVyZSAgClxiZWdpbntlcXVhdGlvbn0KXGJvbGRzeW1ib2x7UH1fcl5cdG9wID0gXHByb2Rfe2k9MX1ebSBcbGVmdChcYm9sZHN5bWJvbHtJfS1yX3sxaX1cZGZyYWN7XGJvbGRzeW1ib2x7TH1cYm9sZHN5bWJvbHtDfV57LTF9fXtca2FwcGFeMn1ccmlnaHQpXHF1YWRcdGV4dHthbmR9XHF1YWQgXGJvbGRzeW1ib2x7UH1fXGVsbF5cdG9wID0gXGRmcmFje1xrYXBwYV57MlxiZXRhfX17XHRleHR0dHtmYWN0b3J9fVxwcm9kX3tqPTF9XnttKzF9IFxsZWZ0KFxib2xkc3ltYm9se0l9LXJfezJqfVxkZnJhY3tcYm9sZHN5bWJvbHtMfVxib2xkc3ltYm9se0N9XnstMX19e1xrYXBwYV4yfVxyaWdodClcY2RvdCBcYm9sZHN5bWJvbHtDfQpcZW5ke2VxdWF0aW9ufQpzaW5jZSAkXGJvbGRzeW1ib2x7TH0kIGFuZCAkXGJvbGRzeW1ib2x7Q31eey0xfSQgYXJlIHN5bW1ldHJpYyBhbmQgdGhlIGZhY3RvcnMgaW4gdGhlIHByb2R1Y3QgY29tbXV0ZS4gUmVwbGFjaW5nIHRoZXNlIHR3byBpbnRvIFxlcXJlZntlcTpzY2hlbWV9IHlpZWxkcwpcYmVnaW57ZXF1YXRpb259ClxsZWZ0KFxwcm9kX3tpPTF9Xm0gXGxlZnQoXGJvbGRzeW1ib2x7SX0tcl97MWl9XGRmcmFje1xib2xkc3ltYm9se0x9XGJvbGRzeW1ib2x7Q31eey0xfX17XGthcHBhXjJ9XHJpZ2h0KStcZGZyYWN7XHRhdSBca2FwcGFeezJcYmV0YX19e1x0ZXh0dHR7ZmFjdG9yfX1ccHJvZF97aj0xfV57bSsxfSBcbGVmdChcYm9sZHN5bWJvbHtJfS1yX3syan1cZGZyYWN7XGJvbGRzeW1ib2x7TH1cYm9sZHN5bWJvbHtDfV57LTF9fXtca2FwcGFeMn1ccmlnaHQpXHJpZ2h0KVxib2xkc3ltYm9se0N9XGJvbGRzeW1ib2x7VX1ee2srMX0gPSBccHJvZF97aT0xfV5tIFxsZWZ0KFxib2xkc3ltYm9se0l9LXJfezFpfVxkZnJhY3tcYm9sZHN5bWJvbHtMfVxib2xkc3ltYm9se0N9XnstMX19e1xrYXBwYV4yfVxyaWdodClcY2RvdCAoXGJvbGRzeW1ib2x7Q31cYm9sZHN5bWJvbHtVfV5rK1x0YXUgXGJvbGRzeW1ib2x7Rn1ee2srMX0pLApcZW5ke2VxdWF0aW9ufQp0aGF0IGlzLApcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTpmaW5hbF9zY2hlbWV9Clx0YWd7OX0KXGJvbGRzeW1ib2x7VX1ee2srMX0gPSBcYm9sZHN5bWJvbHtDfV57LTF9XGxlZnQoXHByb2Rfe2k9MX1ebSBcbGVmdChcYm9sZHN5bWJvbHtJfS1yX3sxaX1cZGZyYWN7XGJvbGRzeW1ib2x7TH1cYm9sZHN5bWJvbHtDfV57LTF9fXtca2FwcGFeMn1ccmlnaHQpK1xkZnJhY3tcdGF1IFxrYXBwYV57MlxiZXRhfX17XHRleHR0dHtmYWN0b3J9fVxwcm9kX3tqPTF9XnttKzF9IFxsZWZ0KFxib2xkc3ltYm9se0l9LXJfezJqfVxkZnJhY3tcYm9sZHN5bWJvbHtMfVxib2xkc3ltYm9se0N9XnstMX19e1xrYXBwYV4yfVxyaWdodClccmlnaHQpXnstMX0gXHByb2Rfe2k9MX1ebSBcbGVmdChcYm9sZHN5bWJvbHtJfS1yX3sxaX1cZGZyYWN7XGJvbGRzeW1ib2x7TH1cYm9sZHN5bWJvbHtDfV57LTF9fXtca2FwcGFeMn1ccmlnaHQpXGNkb3QgKFxib2xkc3ltYm9se0N9XGJvbGRzeW1ib2x7VX1eaytcdGF1IFxib2xkc3ltYm9se0Z9XntrKzF9KS4KXGVuZHtlcXVhdGlvbn0KQ29uc2lkZXJpbmcgdGhlIHBhcnRpYWwgZnJhY3Rpb24gZGVjb21wb3NpdGlvbgpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTpwYXJ0aWFsX2ZyYWN0aW9ufQpcdGFnezEwfQpcZGZyYWN7XHByb2Rfe2k9MX1ebSAoMS1yX3sxaX14KX17XHByb2Rfe2k9MX1ebSAoMS1yX3sxaX14KStcZGZyYWN7XHRhdSBca2FwcGFeezJcYmV0YX19e1x0ZXh0dHR7ZmFjdG9yfX0gXHByb2Rfe2o9MX1ee20rMX0gKDEtcl97Mmp9eCl9PVxzdW1fe2s9MX1ee20rMX0gYV9rKHgtcF9rKV57LTF9ICsgciwKXGVuZHtlcXVhdGlvbn0Kc2NoZW1lIFxlcXJlZntlcTpmaW5hbF9zY2hlbWV9IGNhbiBiZSBleHByZXNzZWQgYXMKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXE6ZmluYWxfc2NoZW1lMn0KXHRhZ3sxMX0KXGJvbGRzeW1ib2x7VX1ee2srMX0gPSBcYm9sZHN5bWJvbHtDfV57LTF9XGxlZnQoXHN1bV97az0xfV57bSsxfSBhX2tcbGVmdCggXGRmcmFje1xib2xkc3ltYm9se0x9XGJvbGRzeW1ib2x7Q31eey0xfX17XGthcHBhXjJ9LXBfa1xib2xkc3ltYm9se0l9XHJpZ2h0KV57LTF9ICsgclxib2xkc3ltYm9se0l9XHJpZ2h0KSAoXGJvbGRzeW1ib2x7Q31cYm9sZHN5bWJvbHtVfV5rK1x0YXUgXGJvbGRzeW1ib2x7Rn1ee2srMX0pClxlbmR7ZXF1YXRpb259CgpJbiBwcmFjdGljZSwgc2luY2UgdGhlIHJhdGlvbmFsIGZ1bmN0aW9uIGluIFxlcXJlZntlcTpwYXJ0aWFsX2ZyYWN0aW9ufSBpcyBwcm9wZXIsIHRoZXJlIGlzIG5vIHJlbWFpbmRlciAkciQuIE1vcmVvdmVyLCBzaW5jZSAkXGxlZnQoIFxkZnJhY3tcYm9sZHN5bWJvbHtMfVxib2xkc3ltYm9se0N9XnstMX19e1xrYXBwYV4yfS1wX2tcYm9sZHN5bWJvbHtJfVxyaWdodCleey0xfSAgPSBcYm9sZHN5bWJvbHtDfVxsZWZ0KCBcZGZyYWN7XGJvbGRzeW1ib2x7TH19e1xrYXBwYV4yfS1wX2tcYm9sZHN5bWJvbHtDfVxyaWdodCleey0xfSQsIHdlIGhhdmUgdGhhdCBcZXFyZWZ7ZXE6ZmluYWxfc2NoZW1lMn0gY2FuIGJlIHJld3JpdHRlbiBhcwoKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXE6ZmluYWxfc2NoZW1lM30KXHRhZ3sxMn0KXGJvbGRzeW1ib2x7VX1ee2srMX0gPSBcbGVmdChcc3VtX3trPTF9XnttKzF9IGFfa1xsZWZ0KCBcZGZyYWN7XGJvbGRzeW1ib2x7TH19e1xrYXBwYV4yfS1wX2tcYm9sZHN5bWJvbHtDfVxyaWdodCleey0xfVxyaWdodCkgKFxib2xkc3ltYm9se0N9XGJvbGRzeW1ib2x7VX1eaytcdGF1IFxib2xkc3ltYm9se0Z9XntrKzF9KS4KXGVuZHtlcXVhdGlvbn0KCiMjIE51bWVyaWNhbCBpbXBsZW1lbnRhdGlvbiB7I251bV9pbXBsZW1lbnRhdGlvbn0KCiMjIyBGdW5jdGlvbiBgbXkuZ2V0LnJvb3RzKClgCgpGb3IgZWFjaCByYXRpb25hbCBvcmRlciAkbSQgKDEsMiwzLDQpIGFuZCBzbW9vdGhuZXNzIHBhcmFtZXRlciAkXGJldGEkICg9ICRcYWxwaGEvMiQgd2l0aCAkXGFscGhhJCBiZXR3ZWVuIDAuNSBhbmQgMiksIGZ1bmN0aW9uIGBteS5nZXQucm9vdHMoKWAgKGFkYXB0ZWQgZnJvbSB0aGUgYHJTUERFYCBwYWNrYWdlKSByZXR1cm5zICRcdGV4dHR0e2ZhY3Rvcn0gPSBcZGZyYWN7Y19tfXtiX3ttKzF9fSQsIGFuZCB0aGUgcm9vdHMgJFx7cl97MWl9XH1fe2k9MX1ebSQgYW5kICRce3JfezJqfVx9X3tqPTF9XnttKzF9JC4KCmBgYHtyfQojIEZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIHJvb3RzIGFuZCBmYWN0b3IgZm9yIHRoZSByYXRpb25hbCBhcHByb3hpbWF0aW9uCm15LmdldC5yb290cyA8LSBmdW5jdGlvbihtLCAjIHJhdGlvbmFsIG9yZGVyLCBtID0gMSwgMiwgMywgb3IgNAogICAgICAgICAgICAgICAgICAgICAgICAgYmV0YSAjIHNtb290aG5lc3MgcGFyYW1ldGVyLCBiZXRhID0gYWxwaGEvMiB3aXRoIGFscGhhIGJldHdlZW4gMC41IGFuZCAyCiAgICAgICAgICAgICAgICAgICAgICAgICApIHsKICBtMXRhYmxlIDwtIHJTUERFOjo6bTF0YWJsZQogIG0ydGFibGUgPC0gclNQREU6OjptMnRhYmxlCiAgbTN0YWJsZSA8LSByU1BERTo6Om0zdGFibGUKICBtNHRhYmxlIDwtIHJTUERFOjo6bTR0YWJsZQogIG10IDwtIGdldChwYXN0ZTAoIm0iLCBtLCAidGFibGUiKSkKICByYiA8LSByZXAoMCwgbSArIDEpCiAgcmMgPC0gcmVwKDAsIG0pCiAgaWYobSA9PSAxKSB7CiAgICByYyA9IGFwcHJveChtdCRiZXRhLCBtdFtbcGFzdGUwKCJyYyIpXV0sIGJldGEpJHkKICB9IGVsc2UgewogICAgcmMgPSBzYXBwbHkoMTptLCBmdW5jdGlvbihpKSB7CiAgICAgIGFwcHJveChtdCRiZXRhLCBtdFtbcGFzdGUwKCJyYy4iLCBpKV1dLCBiZXRhKSR5CiAgICB9KQogIH0KICByYiA9IHNhcHBseSgxOihtKzEpLCBmdW5jdGlvbihpKSB7CiAgICBhcHByb3gobXQkYmV0YSwgbXRbW3Bhc3RlMCgicmIuIiwgaSldXSwgeG91dCA9IGJldGEpJHkKICB9KQogIGZhY3RvciA9IGFwcHJveChtdCRiZXRhLCBtdCRmYWN0b3IsIHhvdXQgPSBiZXRhKSR5CiAgcmV0dXJuKGxpc3QocGxfcm9vdHMgPSByYiwgIyByb290cyBce3JfezJqfVx9X3tqPTF9XnttKzF9CiAgICAgICAgICAgICAgcHJfcm9vdHMgPSByYywgIyByb290cyBce3JfezFpfVx9X3tpPTF9Xm0KICAgICAgICAgICAgICBmYWN0b3IgPSBmYWN0b3IgIyB0aGlzIGlzIGNfbS9iX3ttKzF9CiAgICAgICAgICAgICAgKSkKfQpgYGAKCiMjIyBGdW5jdGlvbiBgcG9seS5mcm9tLnJvb3RzKClgCgpGdW5jdGlvbiBgcG9seS5mcm9tLnJvb3RzKClgIGNvbXB1dGVzIHRoZSBjb2VmZmljaWVudHMgb2YgYSBwb2x5bm9taWFsIGZyb20gaXRzIHJvb3RzLgoKYGBge3J9CiMgRnVuY3Rpb24gdG8gY29tcHV0ZSBwb2x5bm9taWFsIGNvZWZmaWNpZW50cyBmcm9tIHJvb3RzCnBvbHkuZnJvbS5yb290cyA8LSBmdW5jdGlvbihyb290cykgewogIGNvZWYgPC0gMQogIGZvciAociBpbiByb290cykge2NvZWYgPC0gY29udm9sdmUoY29lZiwgYygxLCAtciksIHR5cGUgPSAib3BlbiIpfQogIHJldHVybihjb2VmKSAjIHJldHVybmVkIGluIGluY3JlYXNpbmcgb3JkZXIgbGlrZSBhK2J4K2N4XjIrLi4uCn0KYGBgCgojIyMgRnVuY3Rpb24gYGNvbXB1dGUucGFydGlhbC5mcmFjdGlvbi5wYXJhbSgpYAoKR2l2ZW4gYGZhY3RvcmAkPVx0ZXh0dHR7ZmFjdG9yfSA9IFxkZnJhY3tjX219e2Jfe20rMX19JCwgYHByX3Jvb3RzYCQ9XHtyX3sxaX1cfV97aT0xfV5tJCwgYHBsX3Jvb3RzYCQ9XHtyX3syan1cfV97aj0xfV57bSsxfSQsIGB0aW1lX3N0ZXBgJD1cdGF1JCwgYW5kIGBzY2FsaW5nYCQ9XGthcHBhXnsyXGJldGF9JCwgZnVuY3Rpb24gYGNvbXB1dGUucGFydGlhbC5mcmFjdGlvbi5wYXJhbSgpYCBjb21wdXRlcyB0aGUgcGFyYW1ldGVycyBmb3IgdGhlIHBhcnRpYWwgZnJhY3Rpb24gZGVjb21wb3NpdGlvbiBcZXFyZWZ7ZXE6cGFydGlhbF9mcmFjdGlvbn0uCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBjb21wdXRlIHRoZSBwYXJhbWV0ZXJzIGZvciB0aGUgcGFydGlhbCBmcmFjdGlvbiBkZWNvbXBvc2l0aW9uCmNvbXB1dGUucGFydGlhbC5mcmFjdGlvbi5wYXJhbSA8LSBmdW5jdGlvbihmYWN0b3IsICMgY19tL2Jfe20rMX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByX3Jvb3RzLCAjIHJvb3RzIFx7cl97MWl9XH1fe2k9MX1ebQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxfcm9vdHMsICMgcm9vdHMgXHtyX3syan1cfV97aj0xfV57bSsxfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZV9zdGVwLCAjIFx0YXUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxpbmcgIyBca2FwcGFeezJcYmV0YX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkgewogIHByX2NvZWYgPC0gYygwLCBwb2x5LmZyb20ucm9vdHMocHJfcm9vdHMpKSAKICBwbF9jb2VmIDwtIHBvbHkuZnJvbS5yb290cyhwbF9yb290cykgCiAgZmFjdG9yX3ByX2NvZWYgPC0gcHJfY29lZgogIHByX3BsdXNfcGxfY29lZiA8LSBmYWN0b3JfcHJfY29lZiArICgoc2NhbGluZyAqIHRpbWVfc3RlcCkvZmFjdG9yKSAqIHBsX2NvZWYKICByZXMgPC0gZ3NpZ25hbDo6cmVzaWR1ZShmYWN0b3JfcHJfY29lZiwgcHJfcGx1c19wbF9jb2VmKQogIHJldHVybihsaXN0KHIgPSByZXMkciwgIyByZXNpZHVlcyBce2Ffa1x9X3trPTF9XnttKzF9CiAgICAgICAgICAgICAgcCA9IHJlcyRwLCAjIHBvbGVzIFx7cF9rXH1fe2s9MX1ee20rMX0KICAgICAgICAgICAgICBrID0gcmVzJGsgIyByZW1haW5kZXIgcgogICAgICAgICAgICAgICkpIAp9CmBgYAoKIyMjIEZ1bmN0aW9uIGBteS5mcmFjdGlvbmFsLm9wZXJhdG9ycy5mcmFjKClgCgpHaXZlbiB0aGUgTGFwbGFjaWFuIG1hdHJpeCBgTGAsIHRoZSBzbW9vdGhuZXNzIHBhcmFtZXRlciBgYmV0YWAsIHRoZSBtYXNzIG1hdHJpeCBgQ2AgKG5vdCBsdW1wZWQpLCB0aGUgc2NhbGluZyBmYWN0b3IgYHNjYWxlLmZhY3RvcmAkPVxrYXBwYV4yJCwgdGhlIHJhdGlvbmFsIG9yZGVyIGBtYCwgYW5kIHRoZSB0aW1lIHN0ZXAgYHRpbWVfc3RlcGAkPVx0YXUkLCBmdW5jdGlvbiBgbXkuZnJhY3Rpb25hbC5vcGVyYXRvcnMuZnJhYygpYCBjb21wdXRlcyB0aGUgZnJhY3Rpb25hbCBvcGVyYXRvciBhbmQgcmV0dXJucyBhIGxpc3QgY29udGFpbmluZyB0aGUgbmVjZXNzYXJ5IG1hdHJpY2VzIGFuZCBwYXJhbWV0ZXJzIGZvciB0aGUgZnJhY3Rpb25hbCBkaWZmdXNpb24gZXF1YXRpb24uCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBjb21wdXRlIHRoZSBmcmFjdGlvbmFsIG9wZXJhdG9yCm15LmZyYWN0aW9uYWwub3BlcmF0b3JzLmZyYWMgPC0gZnVuY3Rpb24oTCwgIyBMYXBsYWNpYW4gbWF0cml4CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmV0YSwgIyBzbW9vdGhuZXNzIHBhcmFtZXRlciBiZXRhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQywgIyBtYXNzIG1hdHJpeCAobm90IGx1bXBlZCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZS5mYWN0b3IsICMgc2NhbGluZyBwYXJhbWV0ZXIgPSBrYXBwYV4yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbSA9IDEsICMgcmF0aW9uYWwgb3JkZXIsIG0gPSAxLCAyLCAzLCBvciA0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZV9zdGVwICMgdGltZSBzdGVwID0gdGF1CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSB7CiAgSSA8LSBNYXRyaXg6OkRpYWdvbmFsKGRpbShDKVsxXSkKICBMIDwtIEwgLyBzY2FsZS5mYWN0b3IgCiAgaWYoYmV0YSA9PSAxKXsKICAgIEwgPC0gTCAqIHNjYWxlLmZhY3Rvcl5iZXRhCiAgICByZXR1cm4obGlzdChDID0gQywgIyBtYXNzIG1hdHJpeAogICAgICAgICAgICAgICAgTCA9IEwsICMgTGFwbGFjaWFuIG1hdHJpeCBzY2FsZWQKICAgICAgICAgICAgICAgIG0gPSBtLCAjIHJhdGlvbmFsIG9yZGVyCiAgICAgICAgICAgICAgICBiZXRhID0gYmV0YSwgIyBzbW9vdGhuZXNzIHBhcmFtZXRlcgogICAgICAgICAgICAgICAgTEhTID0gQyArIHRpbWVfc3RlcCAqIEwgIyBsZWZ0LWhhbmQgc2lkZSBvZiB0aGUgbGluZWFyIHN5c3RlbQogICAgICAgICAgICAgICAgKSkKICB9IGVsc2UgewogICAgc2NhbGluZyA8LSBzY2FsZS5mYWN0b3JeYmV0YQogICAgcm9vdHMgPC0gbXkuZ2V0LnJvb3RzKG0sIGJldGEpCiAgICBwb2xlc19yc19rIDwtIGNvbXB1dGUucGFydGlhbC5mcmFjdGlvbi5wYXJhbShyb290cyRmYWN0b3IsIHJvb3RzJHByX3Jvb3RzLCByb290cyRwbF9yb290cywgdGltZV9zdGVwLCBzY2FsaW5nKQoKICAgIHBhcnRpYWxfZnJhY3Rpb25fdGVybXMgPC0gbGlzdCgpCiAgICBmb3IgKGkgaW4gMToobSsxKSkgewogICAgICAjIEhlcmUgaXMgd2hlcmUgdGhlIHRlcm1zIGluIHRoZSBzdW0gaW4gZXEgMTIgYXJlIGNvbXB1dGVkCiAgICAgIHBhcnRpYWxfZnJhY3Rpb25fdGVybXNbW2ldXSA8LSAoTCAtIHBvbGVzX3JzX2skcFtpXSAqIEMpL3BvbGVzX3JzX2skcltpXQogICAgICB9CiAgICByZXR1cm4obGlzdChDID0gQywgIyBtYXNzIG1hdHJpeAogICAgICAgICAgICAgICAgTCA9IEwsICMgTGFwbGFjaWFuIG1hdHJpeCBzY2FsZWQKICAgICAgICAgICAgICAgIG0gPSBtLCAjIHJhdGlvbmFsIG9yZGVyCiAgICAgICAgICAgICAgICBiZXRhID0gYmV0YSwgIyBzbW9vdGhuZXNzIHBhcmFtZXRlcgogICAgICAgICAgICAgICAgcGFydGlhbF9mcmFjdGlvbl90ZXJtcyA9IHBhcnRpYWxfZnJhY3Rpb25fdGVybXMgIyBwYXJ0aWFsIGZyYWN0aW9uIHRlcm1zCiAgICAgICAgICAgICAgICApKQogIH0KfQpgYGAKCiMjIyBGdW5jdGlvbiBgbXkuc29sdmVyLmZyYWMoKWAKCkdpdmVuIHRoZSBvYmplY3QgcmV0dXJuZWQgYnkgYG15LmZyYWN0aW9uYWwub3BlcmF0b3JzLmZyYWMoKWAgYW5kIGEgdmVjdG9yIGB2YCwgZnVuY3Rpb24gYG15LnNvbHZlci5mcmFjKClgIHNvbHZlcyB0aGUgbGluZWFyIHN5c3RlbSBcZXFyZWZ7ZXE6ZmluYWxfc2NoZW1lMn0gZm9yIHRoZSB2ZWN0b3IgYHZgLiBJZiBgYmV0YSA9IDFgLCBpdCBzb2x2ZXMgdGhlIGxpbmVhciBzeXN0ZW0gZGlyZWN0bHk7IG90aGVyd2lzZSwgaXQgdXNlcyB0aGUgcGFydGlhbCBmcmFjdGlvbiBkZWNvbXBvc2l0aW9uLgoKYGBge3J9CiMgRnVuY3Rpb24gdG8gc29sdmUgdGhlIGl0ZXJhdGlvbgpteS5zb2x2ZXIuZnJhYyA8LSBmdW5jdGlvbihvYmosICMgb2JqZWN0IHJldHVybmVkIGJ5IG15LmZyYWN0aW9uYWwub3BlcmF0b3JzLmZyYWMoKQogICAgICAgICAgICAgICAgICAgICAgICAgICB2ICMgdmVjdG9yIHRvIGJlIHNvbHZlZCBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgKXsKICBiZXRhIDwtIG9iaiRiZXRhCiAgbSA8LSBvYmokbQogIGlmIChiZXRhID09IDEpewogICAgcmV0dXJuKHNvbHZlKG9iaiRMSFMsIHYpICMgc29sdmUgdGhlIGxpbmVhciBzeXN0ZW0gZGlyZWN0bHkgZm9yIGJldGEgPSAxCiAgICAgICAgICAgKQogIH0gZWxzZSB7CiAgICBwYXJ0aWFsX2ZyYWN0aW9uX3Rlcm1zIDwtIG9iaiRwYXJ0aWFsX2ZyYWN0aW9uX3Rlcm1zCiAgICBvdXRwdXQgPC0gdiowCiAgICBmb3IgKGkgaW4gMToobSsxKSkge291dHB1dCA8LSBvdXRwdXQgKyBzb2x2ZShwYXJ0aWFsX2ZyYWN0aW9uX3Rlcm1zW1tpXV0sIHYpfQogICAgcmV0dXJuKG91dHB1dCAjIHNvbHZlIHRoZSBsaW5lYXIgc3lzdGVtIHVzaW5nIHRoZSBwYXJ0aWFsIGZyYWN0aW9uIGRlY29tcG9zaXRpb24KICAgICAgICAgICApCiAgfQp9CmBgYAoKCiMjIyBGdW5jdGlvbiBgc29sdmVfZnJhY3Rpb25hbF9ldm9sdXRpb24oKWAKCkdpdmVuIHRoZSBmcmFjdGlvbmFsIG9wZXJhdG9yIG9iamVjdCBgbXlfb3BfZnJhY2AsIGEgdGltZSBzdGVwIGB0aW1lX3N0ZXBgLCBhIHNlcXVlbmNlIG9mIHRpbWUgcG9pbnRzIGB0aW1lX3NlcWAsIGFuIGluaXRpYWwgdmFsdWUgYHZhbF9hdF8wYCwgYW5kIHRoZSByaWdodC1oYW5kIHNpZGUgbWF0cml4IGBSSFNUYCwgZnVuY3Rpb24gYHNvbHZlX2ZyYWN0aW9uYWxfZXZvbHV0aW9uKClgIGNvbXB1dGVzIHRoZSBzb2x1dGlvbiB0byB0aGUgZnJhY3Rpb25hbCBkaWZmdXNpb24gZXF1YXRpb24gYXQgZWFjaCB0aW1lIHN0ZXAgdXNpbmcgc2NoZW1lIFxlcXJlZntlcTpmaW5hbF9zY2hlbWUyfS4KCgpgYGB7cn0Kc29sdmVfZnJhY3Rpb25hbF9ldm9sdXRpb24gPC0gZnVuY3Rpb24obXlfb3BfZnJhYywgdGltZV9zdGVwLCB0aW1lX3NlcSwgdmFsX2F0XzAsIFJIU1QpIHsKICBDQyA8LSBteV9vcF9mcmFjJEMKICBTT0wgPC0gbWF0cml4KE5BLCBucm93ID0gbnJvdyhDQyksIG5jb2wgPSBsZW5ndGgodGltZV9zZXEpKQogIFNPTFssIDFdIDwtIHZhbF9hdF8wCiAgZm9yIChrIGluIDE6KGxlbmd0aCh0aW1lX3NlcSkgLSAxKSkgewogICAgcmhzIDwtIENDICUqJSBTT0xbLCBrXSArIHRpbWVfc3RlcCAqIFJIU1RbLCBrICsgMV0KICAgIFNPTFssIGsgKyAxXSA8LSBhcy5tYXRyaXgobXkuc29sdmVyLmZyYWMobXlfb3BfZnJhYywgcmhzKSkKICB9CiAgcmV0dXJuKFNPTCkKfQpgYGAKCiMjIEF1eGlsaWFyeSBmdW5jdGlvbnMgeyNhdXhpbGlhcnlfZnVuY3Rpb25zfQoKIyMjIEZ1bmN0aW9uIGBnZXRzLmdyYXBoLnRhZHBvbGUoKWAKCkdpdmVuIGEgbWVzaCBzaXplIGBoYCwgZnVuY3Rpb24gYGdldHMuZ3JhcGgudGFkcG9sZSgpYCBidWlsZHMgYSB0YWRwb2xlIGdyYXBoIGFuZCBjcmVhdGVzIGEgbWVzaC4KCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBidWlsZCBhIHRhZHBvbGUgZ3JhcGggYW5kIGNyZWF0ZSBhIG1lc2gKZ2V0cy5ncmFwaC50YWRwb2xlIDwtIGZ1bmN0aW9uKGgpewogIGVkZ2UxIDwtIHJiaW5kKGMoMCwwKSxjKDEsMCkpCiAgdGhldGEgPC0gc2VxKGZyb209LXBpLHRvPXBpLGxlbmd0aC5vdXQgPSAxMDAwMCkKICBlZGdlMiA8LSBjYmluZCgxKzEvcGkrY29zKHRoZXRhKS9waSxzaW4odGhldGEpL3BpKQogIGVkZ2VzIDwtIGxpc3QoZWRnZTEsIGVkZ2UyKQogIGdyYXBoIDwtIG1ldHJpY19ncmFwaCRuZXcoZWRnZXMgPSBlZGdlcywgdmVyYm9zZSA9IDApCiAgZ3JhcGgkc2V0X21hbnVhbF9lZGdlX2xlbmd0aHMoZWRnZV9sZW5ndGhzID0gYygxLDIpKQogIGdyYXBoJGJ1aWxkX21lc2goaCA9IGgpCiAgcmV0dXJuKGdyYXBoKQp9CmBgYAoKCiMjIyBGdW5jdGlvbiBgdGFkcG9sZS5laWcoKWAKCkdpdmVuIGEgbW9kZSBudW1iZXIgYGtgIGFuZCBhIHRhZHBvbGUgZ3JhcGggYGdyYXBoYCwgZnVuY3Rpb24gYHRhZHBvbGUuZWlnKClgIGNvbXB1dGVzIHRoZSBlaWdlbnBhaXJzIG9mIHRoZSB0YWRwb2xlIGdyYXBoLgoKCmBgYHtyfQojIEZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIGVpZ2VuZnVuY3Rpb25zIG9mIHRoZSB0YWRwb2xlIGdyYXBoCnRhZHBvbGUuZWlnIDwtIGZ1bmN0aW9uKGssZ3JhcGgpewp4MSA8LSBjKDAsZ3JhcGgkZ2V0X2VkZ2VfbGVuZ3RocygpWzFdKmdyYXBoJG1lc2gkUHRFW2dyYXBoJG1lc2gkUHRFWywxXT09MSwyXSkgCngyIDwtIGMoMCxncmFwaCRnZXRfZWRnZV9sZW5ndGhzKClbMl0qZ3JhcGgkbWVzaCRQdEVbZ3JhcGgkbWVzaCRQdEVbLDFdPT0yLDJdKSAKCmlmKGs9PTApeyAKICBmLmUxIDwtIHJlcCgxLGxlbmd0aCh4MSkpIAogIGYuZTIgPC0gcmVwKDEsbGVuZ3RoKHgyKSkgCiAgZjEgPSBjKGYuZTFbMV0sZi5lMlsxXSxmLmUxWy0xXSwgZi5lMlstMV0pIAogIGYgPSBsaXN0KHBoaT1mMS9zcXJ0KDMpKSAKICAKfSBlbHNlIHsKICBmLmUxIDwtIC0yKnNpbihwaSprKjEvMikqY29zKHBpKmsqeDEvMikgCiAgZi5lMiA8LSBzaW4ocGkqayp4Mi8yKSAgICAgICAgICAgICAgICAgIAogIAogIGYxID0gYyhmLmUxWzFdLGYuZTJbMV0sZi5lMVstMV0sIGYuZTJbLTFdKSAKICAKICBpZigoayAlJSAyKT09MSl7IAogICAgZiA9IGxpc3QocGhpPWYxL3NxcnQoMykpIAogIH0gZWxzZSB7IAogICAgZi5lMSA8LSAoLTEpXntrLzJ9KmNvcyhwaSprKngxLzIpCiAgICBmLmUyIDwtIGNvcyhwaSprKngyLzIpCiAgICBmMiA9IGMoZi5lMVsxXSxmLmUyWzFdLGYuZTFbLTFdLGYuZTJbLTFdKSAKICAgIGYgPC0gbGlzdChwaGk9ZjEscHNpPWYyL3NxcnQoMy8yKSkKICB9Cn0KcmV0dXJuKGYpCn0KYGBgCgojIyMgRnVuY3Rpb24gYGdldHMuZWlnZW4ucGFyYW1zKClgCgpHaXZlbiBhIGZpbml0ZSBudW1iZXIgb2YgbW9kZXMgYE5fZmluaXRlYCwgYSBzY2FsaW5nIHBhcmFtZXRlciBga2FwcGFgLCBhIHNtb290aG5lc3MgcGFyYW1ldGVyIGBhbHBoYWAsIGFuZCBhIHRhZHBvbGUgZ3JhcGggYGdyYXBoYCwgZnVuY3Rpb24gYGdldHMuZWlnZW4ucGFyYW1zKClgIGNvbXB1dGVzIGBFSUdFTlZBTF9BTFBIQWAgKGEgdmVjdG9yIHdpdGggZW50cmllcyAkXGxhbWJkYV9qXntcYWxwaGEvMn0kKSwgYEVJR0VOVkFMX01JTlVTX0FMUEhBYCAoYSB2ZWN0b3Igd2l0aCBlbnRyaWVzICRcbGFtYmRhX2peey1cYWxwaGEvMn0kKSwgYW5kIGBFSUdFTkZVTmAgKGEgbWF0cml4IHdpdGggY29sdW1ucyAkZV9qJCBvbiB0aGUgbWVzaCBvZiBgZ3JhcGhgKS4KCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBjb21wdXRlIHRoZSBlaWdlbnBhaXJzIG9mIHRoZSB0YWRwb2xlIGdyYXBoCmdldHMuZWlnZW4ucGFyYW1zIDwtIGZ1bmN0aW9uKE5fZmluaXRlID0gNCwga2FwcGEgPSAxLCBhbHBoYSA9IDAuNSwgZ3JhcGgpewogIEVJR0VOVkFMIDwtIE5VTEwKICBFSUdFTlZBTF9BTFBIQSA8LSBOVUxMCiAgRUlHRU5WQUxfTUlOVVNfQUxQSEEgPC0gTlVMTAogIEVJR0VORlVOIDwtIE5VTEwKICBJTkRFWCA8LSBOVUxMCiAgZm9yIChqIGluIDA6Tl9maW5pdGUpIHsKICAgIGxhbWJkYV9qIDwtIGthcHBhXjIgKyAoaipwaS8yKV4yCiAgICBsYW1iZGFfal9hbHBoYV9oYWxmIDwtIGxhbWJkYV9qXihhbHBoYS8yKQogICAgbGFtYmRhX2pfbWludXNfYWxwaGFfaGFsZiA8LSBsYW1iZGFfal4oLWFscGhhLzIpCiAgICBlX2ogPC0gdGFkcG9sZS5laWcoaixncmFwaCkkcGhpCiAgICBFSUdFTlZBTCA8LSBjKEVJR0VOVkFMLCBsYW1iZGFfaikKICAgIEVJR0VOVkFMX0FMUEhBIDwtIGMoRUlHRU5WQUxfQUxQSEEsIGxhbWJkYV9qX2FscGhhX2hhbGYpICAKICAgIEVJR0VOVkFMX01JTlVTX0FMUEhBIDwtIGMoRUlHRU5WQUxfTUlOVVNfQUxQSEEsIGxhbWJkYV9qX21pbnVzX2FscGhhX2hhbGYpCiAgICBFSUdFTkZVTiA8LSBjYmluZChFSUdFTkZVTiwgZV9qKQogICAgSU5ERVggPC0gYyhJTkRFWCwgaikKICAgIGlmIChqPjAgJiYgKGogJSUgMiA9PSAwKSkgewogICAgICBsYW1iZGFfaiA8LSBrYXBwYV4yICsgKGoqcGkvMileMgogICAgICBsYW1iZGFfal9hbHBoYV9oYWxmIDwtIGxhbWJkYV9qXihhbHBoYS8yKQogICAgICBsYW1iZGFfal9taW51c19hbHBoYV9oYWxmIDwtIGxhbWJkYV9qXigtYWxwaGEvMikKICAgICAgZV9qIDwtIHRhZHBvbGUuZWlnKGosZ3JhcGgpJHBzaQogICAgICBFSUdFTlZBTCA8LSBjKEVJR0VOVkFMLCBsYW1iZGFfaikKICAgICAgRUlHRU5WQUxfQUxQSEEgPC0gYyhFSUdFTlZBTF9BTFBIQSwgbGFtYmRhX2pfYWxwaGFfaGFsZikgICAgCiAgICAgIEVJR0VOVkFMX01JTlVTX0FMUEhBIDwtIGMoRUlHRU5WQUxfTUlOVVNfQUxQSEEsIGxhbWJkYV9qX21pbnVzX2FscGhhX2hhbGYpCiAgICAgIEVJR0VORlVOIDwtIGNiaW5kKEVJR0VORlVOLCBlX2opCiAgICAgIElOREVYIDwtIGMoSU5ERVgsIGorMC4xKQogICAgICB9CiAgICB9CiAgcmV0dXJuKGxpc3QoRUlHRU5WQUwgPSBFSUdFTlZBTCwKICAgICAgICAgICAgICBFSUdFTlZBTF9BTFBIQSA9IEVJR0VOVkFMX0FMUEhBLCAKICAgICAgICAgICAgICBFSUdFTlZBTF9NSU5VU19BTFBIQSA9IEVJR0VOVkFMX01JTlVTX0FMUEhBLAogICAgICAgICAgICAgIEVJR0VORlVOID0gRUlHRU5GVU4sCiAgICAgICAgICAgICAgSU5ERVggPSBJTkRFWCkpCn0KYGBgCgoKIyMjIEZ1bmN0aW9uIGBjb25zdHJ1Y3RfcGllY2V3aXNlX3Byb2plY3Rpb24oKWAgeyNjb25zdHJ1Y3RfcGllY2V3aXNlX3Byb2plY3Rpb259CgpHaXZlbiBhIG1hdHJpeCBgcHJvamVjdGVkX1VfYXBwcm94YCB3aXRoIGFwcHJveGltYXRlZCB2YWx1ZXMgYXQgZGlzY3JldGUgdGltZSBwb2ludHMsIGEgc2VxdWVuY2Ugb2YgdGltZSBwb2ludHMgYHRpbWVfc2VxYCwgYW5kIGFuIGV4dGVuZGVkIHNlcXVlbmNlIG9mIHRpbWUgcG9pbnRzIGBvdmVya2lsbF90aW1lX3NlcWAsIGZ1bmN0aW9uIGBjb25zdHJ1Y3RfcGllY2V3aXNlX3Byb2plY3Rpb24oKWAgY29uc3RydWN0cyBhIHBpZWNld2lzZSBjb25zdGFudCBwcm9qZWN0aW9uIG9mIHRoZSBhcHByb3hpbWF0ZWQgdmFsdWVzIG92ZXIgdGhlIGV4dGVuZGVkIHRpbWUgc2VxdWVuY2UuCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBjb25zdHJ1Y3QgYSBwaWVjZXdpc2UgY29uc3RhbnQgcHJvamVjdGlvbiBvZiBhcHByb3hpbWF0ZWQgdmFsdWVzCmNvbnN0cnVjdF9waWVjZXdpc2VfcHJvamVjdGlvbiA8LSBmdW5jdGlvbihwcm9qZWN0ZWRfVV9hcHByb3gsIHRpbWVfc2VxLCBvdmVya2lsbF90aW1lX3NlcSkgewogIHByb2plY3RlZF9VX3BpZWNld2lzZSA8LSBtYXRyaXgoTkEsIG5yb3cgPSBucm93KHByb2plY3RlZF9VX2FwcHJveCksIG5jb2wgPSBsZW5ndGgob3ZlcmtpbGxfdGltZV9zZXEpKQogIAogICMgQXNzaWduIHZhbHVlIGF0IHQgPSAwCiAgcHJvamVjdGVkX1VfcGllY2V3aXNlWywgd2hpY2gob3ZlcmtpbGxfdGltZV9zZXEgPT0gMCldIDwtIHByb2plY3RlZF9VX2FwcHJveFssIDFdCiAgCiAgIyBBc3NpZ24gdmFsdWVzIGZvciBpbnRlcnZhbHMgKHRfe2stMX0sIHRfa10KICBmb3IgKGsgaW4gMjpsZW5ndGgodGltZV9zZXEpKSB7CiAgICBpZHhzIDwtIHdoaWNoKG92ZXJraWxsX3RpbWVfc2VxID4gdGltZV9zZXFbayAtIDFdICYgb3ZlcmtpbGxfdGltZV9zZXEgPD0gdGltZV9zZXFba10pCiAgICBwcm9qZWN0ZWRfVV9waWVjZXdpc2VbLCBpZHhzXSA8LSBwcm9qZWN0ZWRfVV9hcHByb3hbLCBrXQogIH0KICAKICByZXR1cm4ocHJvamVjdGVkX1VfcGllY2V3aXNlKQp9CmBgYAoKIyMjIEZ1bmN0aW9ucyBmb3IgY29tcHV0aW5nIHRoZSB0cnVlIGxpbmUgcmF0ZXMKCmBgYHtyfQpsb2dsb2dfbGluZV9lcXVhdGlvbiA8LSBmdW5jdGlvbih4MSwgeTEsIHNsb3BlKSB7CiAgYiA8LSBsb2cxMCh5MSAvICh4MSBeIHNsb3BlKSkKICAKICBmdW5jdGlvbih4KSB7CiAgICAoeCBeIHNsb3BlKSAqICgxMCBeIGIpCiAgfQp9CmV4cF9saW5lX2VxdWF0aW9uIDwtIGZ1bmN0aW9uKHgxLCB5MSwgc2xvcGUpIHsKICBsbkMgPC0gbG9nKHkxKSAtIHNsb3BlICogeDEKICAKICBmdW5jdGlvbih4KSB7CiAgICBleHAobG5DICsgc2xvcGUgKiB4KQogIH0KfQpjb21wdXRlX2d1aWRpbmdfbGluZXMgPC0gZnVuY3Rpb24oeF9heGlzX3ZlY3RvciwgZXJyb3JzLCB0aGVvcmV0aWNhbF9yYXRlcywgbGluZV9lcXVhdGlvbl9mdW4pIHsKICBndWlkaW5nX2xpbmVzIDwtIG1hdHJpeChOQSwgbnJvdyA9IGxlbmd0aCh4X2F4aXNfdmVjdG9yKSwgbmNvbCA9IGxlbmd0aCh0aGVvcmV0aWNhbF9yYXRlcykpCiAgCiAgZm9yIChqIGluIHNlcV9hbG9uZyh0aGVvcmV0aWNhbF9yYXRlcykpIHsKICAgIGd1aWRpbmdfbGluZXNfYXV4IDwtIG1hdHJpeChOQSwgbnJvdyA9IGxlbmd0aCh4X2F4aXNfdmVjdG9yKSwgbmNvbCA9IGxlbmd0aCh4X2F4aXNfdmVjdG9yKSkKICAgIAogICAgZm9yIChrIGluIHNlcV9hbG9uZyh4X2F4aXNfdmVjdG9yKSkgewogICAgICBwb2ludF94MSA8LSB4X2F4aXNfdmVjdG9yW2tdCiAgICAgIHBvaW50X3kxIDwtIGVycm9yc1trLCBqXQogICAgICBzbG9wZSA8LSB0aGVvcmV0aWNhbF9yYXRlc1tqXQogICAgICAKICAgICAgbGluZSA8LSBsaW5lX2VxdWF0aW9uX2Z1bih4MSA9IHBvaW50X3gxLCB5MSA9IHBvaW50X3kxLCBzbG9wZSA9IHNsb3BlKQogICAgICBndWlkaW5nX2xpbmVzX2F1eFssIGtdIDwtIGxpbmUoeF9heGlzX3ZlY3RvcikKICAgIH0KICAgIAogICAgZ3VpZGluZ19saW5lc1ssIGpdIDwtIHJvd01lYW5zKGd1aWRpbmdfbGluZXNfYXV4KQogIH0KICAKICByZXR1cm4oZ3VpZGluZ19saW5lcykKfQpgYGAKCgoKIyMjIEZ1bmN0aW9ucyBmb3IgY29tcHV0aW5nIGFuIGV4YWN0IHNvbHV0aW9uIHRvIHRoZSBmcmFjdGlvbmFsIGRpZmZ1c2lvbiBlcXVhdGlvbiB7I2V4YWN0X3NvbHV0aW9ufQoKQmVsb3cgd2UgcHJlc2VudCBjbG9zZWQtZm9ybSBleHByZXNzaW9ucyBmb3IgJFxkaXNwbGF5c3R5bGUgR19qKHQpPSBcaW50XzBedCBlXntcbGFtYmRhXntcYWxwaGEvMn1fanJ9ZyhyKWRyJCBjb3JyZXNwb25kaW5nIHRvIHNvbWUgY2hvaWNlcyBvZiAkZyhyKSQuCgpcYmVnaW57YWxpZ25lZH0KZyhyKSAmPSBBZV57LVxsYW1iZGFee1xhbHBoYS8yfV9qIHJ9ICZcaW1wbGllc1xxdWFkIEdfaih0KSAmPSBBdCwgXHF1YWQgQVxpblxtYXRoYmJ7Un0gXFxbMS41ZXhdCmcocikgJj0gQWVee1xtdSByfSAmXGltcGxpZXNccXVhZCBHX2oodCkgJj0gQSBcZnJhY3tlXnsoXGxhbWJkYV57XGFscGhhLzJ9X2orXG11KXR9IC0gMX17XGxhbWJkYV57XGFscGhhLzJ9X2ogKyBcbXV9LCBccXVhZCAtXGxhbWJkYV57XGFscGhhLzJ9X2ogXG5lIFxtdSBcaW4gXG1hdGhiYntSfSBcXFsxLjVleF0KZyhyKSAmPSBBcl5uICZcaW1wbGllc1xxdWFkIEdfaih0KSAmPSBBXGZyYWN7KC0xKV57bisxfSBuIX17KFxsYW1iZGFfal57XGFscGhhLzJ9KV57bisxfX0gXGxlZnQoMSAtIGVee1xsYW1iZGFfal57XGFscGhhLzJ9IHR9IFxzdW1fe2s9MH1ebiBcZnJhY3soLVxsYW1iZGFfal57XGFscGhhLzJ9IHQpXmt9e2shfSBccmlnaHQpLCBccXVhZCBuPTAsMSxcZG90cyBcXFsxLjVleF0KZyhyKSAmPSBBXHNpbihcb21lZ2EgcikgJlxpbXBsaWVzXHF1YWQgR19qKHQpICY9IEEgXGZyYWN7ZV57XGxhbWJkYV9qXntcYWxwaGEvMn0gdH0gXGxlZnQoIFxsYW1iZGFfal57XGFscGhhLzJ9IFxzaW4oXG9tZWdhIHQpIC0gXG9tZWdhIFxjb3MoXG9tZWdhIHQpIFxyaWdodCkgKyBcb21lZ2F9eyhcbGFtYmRhX2pee1xhbHBoYS8yfSleMiArIFxvbWVnYV4yfSwgXHF1YWQgXG9tZWdhIFxpbiBcbWF0aGJie1J9IFxcWzEuNWV4XQpnKHIpICY9IEFcY29zKFx0aGV0YSByKSAmXGltcGxpZXNccXVhZCBHX2oodCkgJj0gQSBcZnJhY3tlXntcbGFtYmRhX2pee1xhbHBoYS8yfSB0fSBcbGVmdCggXGxhbWJkYV9qXntcYWxwaGEvMn0gXGNvcyhcdGhldGEgdCkgKyBcdGhldGEgXHNpbihcdGhldGEgdCkgXHJpZ2h0KSAtIFxsYW1iZGFfal57XGFscGhhLzJ9fXsoXGxhbWJkYV9qXntcYWxwaGEvMn0pXjIgKyBcdGhldGFeMn0sIFxxdWFkIFx0aGV0YSBcaW4gXG1hdGhiYntSfQpcZW5ke2FsaWduZWR9CgoKYGBge3J9CiMgRnVuY3Rpb25zIHRvIGNvbXB1dGUgdGhlIGV4YWN0IHNvbHV0aW9uIHRvIHRoZSBmcmFjdGlvbmFsIGRpZmZ1c2lvbiBlcXVhdGlvbgpnX2xpbmVhciA8LSBmdW5jdGlvbihyLCBBLCBsYW1iZGFfal9hbHBoYV9oYWxmKSB7CiAgcmV0dXJuKEEgKiBleHAoLWxhbWJkYV9qX2FscGhhX2hhbGYgKiByKSkKICB9CkdfbGluZWFyIDwtIGZ1bmN0aW9uKHQsIEEpIHsKICByZXR1cm4oQSAqIHQpCiAgfQpnX2V4cCA8LSBmdW5jdGlvbihyLCBBLCBtdSkgewogIHJldHVybihBICogZXhwKG11ICogcikpCiAgfQpHX2V4cCA8LSBmdW5jdGlvbih0LCBBLCBsYW1iZGFfal9hbHBoYV9oYWxmLCBtdSkgewogIGV4cG9uZW50IDwtIGxhbWJkYV9qX2FscGhhX2hhbGYgKyBtdQogIHJldHVybihBICogKGV4cChleHBvbmVudCAqIHQpIC0gMSkgLyBleHBvbmVudCkKICB9CmdfcG9seSA8LSBmdW5jdGlvbihyLCBBLCBuKSB7CiAgcmV0dXJuKEEgKiByXm4pCn0KR19wb2x5IDwtIGZ1bmN0aW9uKHQsIEEsIGxhbWJkYV9qX2FscGhhX2hhbGYsIG4pIHsKICB0IDwtIGFzLnZlY3Rvcih0KQogIGtfdmFscyA8LSAwOm4KICBzdW1fdGVybSA8LSBzYXBwbHkodCwgZnVuY3Rpb24odHQpIHsKICAgIHN1bSgoKC1sYW1iZGFfal9hbHBoYV9oYWxmICogdHQpXmtfdmFscykgLyBmYWN0b3JpYWwoa192YWxzKSkKICB9KQogIGNvZWZmIDwtICgoLTEpXihuICsgMSkpICogZmFjdG9yaWFsKG4pIC8gKGxhbWJkYV9qX2FscGhhX2hhbGZeKG4gKyAxKSkKICByZXR1cm4oQSAqIGNvZWZmICogKDEgLSBleHAobGFtYmRhX2pfYWxwaGFfaGFsZiAqIHQpICogc3VtX3Rlcm0pKQp9Cmdfc2luIDwtIGZ1bmN0aW9uKHIsIEEsIG9tZWdhKSB7CiAgcmV0dXJuKEEgKiBzaW4ob21lZ2EgKiByKSkKfQpHX3NpbiA8LSBmdW5jdGlvbih0LCBBLCBsYW1iZGFfal9hbHBoYV9oYWxmLCBvbWVnYSkgewogIGRlbm9tIDwtIGxhbWJkYV9qX2FscGhhX2hhbGZeMiArIG9tZWdhXjIKICBudW1lcmF0b3IgPC0gZXhwKGxhbWJkYV9qX2FscGhhX2hhbGYgKiB0KSAqIChsYW1iZGFfal9hbHBoYV9oYWxmICogc2luKG9tZWdhICogdCkgLSBvbWVnYSAqIGNvcyhvbWVnYSAqIHQpKSArIG9tZWdhCiAgcmV0dXJuKEEgKiBudW1lcmF0b3IgLyBkZW5vbSkKfQpnX2NvcyA8LSBmdW5jdGlvbihyLCBBLCB0aGV0YSkgewogIHJldHVybihBICogY29zKHRoZXRhICogcikpIAp9CkdfY29zIDwtIGZ1bmN0aW9uKHQsIEEsIGxhbWJkYV9qX2FscGhhX2hhbGYsIHRoZXRhKSB7CiAgZGVub20gPC0gbGFtYmRhX2pfYWxwaGFfaGFsZl4yICsgdGhldGFeMgogIG51bWVyYXRvciA8LSBleHAobGFtYmRhX2pfYWxwaGFfaGFsZiAqIHQpICogKGxhbWJkYV9qX2FscGhhX2hhbGYgKiBjb3ModGhldGEgKiB0KSArIHRoZXRhICogc2luKHRoZXRhICogdCkpIC0gbGFtYmRhX2pfYWxwaGFfaGFsZgogIHJldHVybihBICogbnVtZXJhdG9yIC8gZGVub20pCn0KYGBgCgojIyMgRnVuY3Rpb24gYHJldmVyc2Vjb2x1bW5zKClgCgpHaXZlbiBhIG1hdHJpeCBgbWF0YCwgZnVuY3Rpb24gYHJldmVyc2Vjb2x1bW5zKClgIHJldmVyc2VzIHRoZSBvcmRlciBvZiBpdHMgY29sdW1ucy4KCgpgYGB7cn0KcmV2ZXJzZWNvbHVtbnMgPC0gZnVuY3Rpb24obWF0KSB7CiAgcmV0dXJuKG1hdFssIHJldihzZXFfbGVuKG5jb2wobWF0KSkpXSkKfQpgYGAKCgpgYGB7cn0KIyBoZWxwZXI6IG1lYXN1cmUgY2hhbmdlIHJlbGF0aXZlIHRvIHRoZSBzaXplIG9mIHRoZSBwcmV2aW91cyBpdGVyYXRlIApjaGFuZ2VfY29tcGFyZXIgPC0gZnVuY3Rpb24oWF9uZXcsIFhfb2xkLCB0aW1lX3N0ZXAsIHRpbWVfc2VxLCB3ZWlnaHRzLCByZWxhdGl2ZSA9IFRSVUUpIHsKICBudW0gPC0gc3FydChhcy5kb3VibGUodCh3ZWlnaHRzKSAlKiUgKChYX25ldyAtIFhfb2xkKV4yKSAlKiUgcmVwKHRpbWVfc3RlcCwgbGVuZ3RoKHRpbWVfc2VxKSkpKQogIGlmICghcmVsYXRpdmUpIHsKICAgIHJldHVybihudW0pCiAgICB9CiAgZGVuIDwtIHNxcnQoYXMuZG91YmxlKHQod2VpZ2h0cykgJSolIChYX25ld14yKSAlKiUgcmVwKHRpbWVfc3RlcCwgbGVuZ3RoKHRpbWVfc2VxKSkpKQogIGlmIChkZW4gPCAuTWFjaGluZSRkb3VibGUuZXBzKSB7CiAgICByZXR1cm4oaWZlbHNlKG51bSA8IC5NYWNoaW5lJGRvdWJsZS5lcHMsIDAsIG51bSkpCiAgfSBlbHNlIHsKICAgIHJldHVybihudW0gLyBkZW4pCiAgfQp9CmBgYAoKCmBgYHtyfQojIENvdXBsZWQgc29sdmVyIHdpdGggbXVsdGktY3JpdGVyaWEgY29udmVyZ2VuY2UKc29sdmVfY291cGxlZF9zeXN0ZW1fbXVsdGlfdG9sIDwtIGZ1bmN0aW9uKAogIG15X29wX2ZyYWMsICAgICAgICAgICAjIG9wZXJhdG9yCiAgdGltZV9zdGVwLCAgICAgICAgICAgICMgdGF1CiAgdGltZV9zZXEsICAgICAgICAgICAgICMgdmVjdG9yIG9mIHRpbWVzIAogIHVfMCwgICAgICAgICAgICAgICAgICAjIGluaXRpYWwgc3RhdGUgVV4wIAogIEZfcHJvaiwgICAgICAgICAgICAgICAjIG1hdHJpeCBvZiBGIAogIFZfZCwgICAgICAgICAgICAgICAgICAjIG1hdHJpeCBvZiAKICBQc2ksICAgICAgICAgICAgICAgICAgIyBQc2kgbWF0cml4CiAgUiwgICAgICAgICAgICAgICAgICAgICMgUiBtYXRyaXgKICBBLCBCLCAgICAgICAgICAgICAgICAgIyBsb3dlci91cHBlciBib3VuZHMgKHZlY3RvciBvciBtYXRyaXggYnJvYWRjYXN0YWJsZSB0byB0aW1lIGdyaWQpCiAgbXUsICAgICAgICAgICAgICAgICAgICMgcG9zaXRpdmUgc2NhbGFyCiAgd2VpZ2h0cywKICB0b2wgPSAxZS04LCAgICAgICAgICAgIyBzY2FsYXIgb3IgbmFtZWQgbGlzdDogbGlzdChaPS4uLiwgVT0uLi4sIFA9Li4uKQogIG1heGl0ID0gMjAwLAogIHZlcmJvc2UgPSBGQUxTRSwKICB0cnVlX3NvbAopIHsKCiAgaWYgKGlzLm51bWVyaWModG9sKSAmJiBsZW5ndGgodG9sKSA9PSAxKSB7CiAgICB0b2xfbGlzdCA8LSBsaXN0KFogPSB0b2wsIFUgPSB0b2wsIFAgPSB0b2wpCiAgfSBlbHNlIGlmIChpcy5saXN0KHRvbCkpIHsKICAgIHRvbF9saXN0IDwtIG1vZGlmeUxpc3QobGlzdChaID0gMWUtOCwgVSA9IDFlLTgsIFAgPSAxZS04KSwgdG9sKQogIH0gZWxzZSBzdG9wKCJ0b2wgbXVzdCBiZSBzY2FsYXIgb3IgbGlzdChaPS4uLixVPS4uLixQPS4uLikiKQoKICBpdCA8LSAwCiAgY29udmVyZ2VkIDwtIEZBTFNFCiAgCiAgcmVsX2hpc3RvcnkgPC0gZGF0YS5mcmFtZShpdGVyID0gaW50ZWdlcigwKSwgdmFyaWFibGUgPSBjaGFyYWN0ZXIoMCksIHZhbHVlID0gbnVtZXJpYygwKSkKICBhYnNfaGlzdG9yeSA8LSBkYXRhLmZyYW1lKGl0ZXIgPSBpbnRlZ2VyKDApLCB2YXJpYWJsZSA9IGNoYXJhY3RlcigwKSwgdmFsdWUgPSBudW1lcmljKDApKQoKICB6X3ByZXYgPC0gRl9wcm9qKjEwMAogIFpfbWF0IDwtIFIgJSolIFBzaSAlKiUgel9wcmV2CiAgVV9wcmV2IDwtIEZfcHJvaioxMDAKICBQX3ByZXYgPC0gRl9wcm9qKjEwMAoKICByZXBlYXQgewogICAgaXQgPC0gaXQgKyAxCgogICAgVV9tYXQgPC0gc29sdmVfZnJhY3Rpb25hbF9ldm9sdXRpb24obXlfb3BfZnJhYywgdGltZV9zdGVwLCB0aW1lX3NlcSwgdmFsX2F0XzAgPSB1XzAsIFJIU1QgPSBGX3Byb2ogKyBaX21hdCkKICAgIFZfbWF0IDwtIHJldmVyc2Vjb2x1bW5zKFIgJSolIFBzaSAlKiUgVV9tYXQpCiAgICBRX21hdCA8LSBzb2x2ZV9mcmFjdGlvbmFsX2V2b2x1dGlvbihteV9vcF9mcmFjLCB0aW1lX3N0ZXAsIHRpbWVfc2VxLCB2YWxfYXRfMCA9IHVfMCAqIDAsIFJIU1QgPSBWX21hdCAtIFZfZCkKICAgIFBfbWF0IDwtIHJldmVyc2Vjb2x1bW5zKFFfbWF0KQogICAgel9uZXcgPC0gcG1heChBLCBwbWluKEIsIC0gUF9tYXQgLyBtdSkpCiAgICBaX21hdCA8LSBSICUqJSBQc2kgJSolIHpfbmV3CiAgICAKICAgICMgcmVsYXRpdmUgY2hhbmdlcwogICAgcmVsX2NoYW5nZXNfWiA8LSBjaGFuZ2VfY29tcGFyZXIoel9uZXcsIHpfcHJldiwgdGltZV9zdGVwLCB0aW1lX3NlcSwgd2VpZ2h0cywgcmVsYXRpdmUgPSBUUlVFKSAgCiAgICByZWxfY2hhbmdlc19VIDwtIGNoYW5nZV9jb21wYXJlcihVX21hdCwgVV9wcmV2LCB0aW1lX3N0ZXAsIHRpbWVfc2VxLCB3ZWlnaHRzLCByZWxhdGl2ZSA9IFRSVUUpCiAgICByZWxfY2hhbmdlc19QIDwtIGNoYW5nZV9jb21wYXJlcihQX21hdCwgUF9wcmV2LCB0aW1lX3N0ZXAsIHRpbWVfc2VxLCB3ZWlnaHRzLCByZWxhdGl2ZSA9IFRSVUUpCiAgICBhYnNfY2hhbmdlc19aIDwtIGNoYW5nZV9jb21wYXJlcih6X25ldywgdHJ1ZV9zb2wkel9iYXIsIHRpbWVfc3RlcCwgdGltZV9zZXEsIHdlaWdodHMsIHJlbGF0aXZlID0gRkFMU0UpCiAgICBhYnNfY2hhbmdlc19VIDwtIGNoYW5nZV9jb21wYXJlcihVX21hdCwgdHJ1ZV9zb2wkdV9iYXIsIHRpbWVfc3RlcCwgdGltZV9zZXEsIHdlaWdodHMsIHJlbGF0aXZlID0gRkFMU0UpCiAgICBhYnNfY2hhbmdlc19QIDwtIGNoYW5nZV9jb21wYXJlcihQX21hdCwgdHJ1ZV9zb2wkcF9iYXIsIHRpbWVfc3RlcCwgdGltZV9zZXEsIHdlaWdodHMsIHJlbGF0aXZlID0gRkFMU0UpCgogICAgcmVsX2hpc3RvcnkgPC0gcmJpbmQocmVsX2hpc3RvcnksCiAgICAgIGRhdGEuZnJhbWUoaXRlciA9IGl0LCB2YXJpYWJsZSA9ICJaIiwgdmFsdWUgPSByZWxfY2hhbmdlc19aKSwKICAgICAgZGF0YS5mcmFtZShpdGVyID0gaXQsIHZhcmlhYmxlID0gIlUiLCB2YWx1ZSA9IHJlbF9jaGFuZ2VzX1UpLAogICAgICBkYXRhLmZyYW1lKGl0ZXIgPSBpdCwgdmFyaWFibGUgPSAiUCIsIHZhbHVlID0gcmVsX2NoYW5nZXNfUCkpCiAgICBhYnNfaGlzdG9yeSA8LSByYmluZChhYnNfaGlzdG9yeSwKICAgICAgZGF0YS5mcmFtZShpdGVyID0gaXQsIHZhcmlhYmxlID0gIloiLCB2YWx1ZSA9IGFic19jaGFuZ2VzX1opLAogICAgICBkYXRhLmZyYW1lKGl0ZXIgPSBpdCwgdmFyaWFibGUgPSAiVSIsIHZhbHVlID0gYWJzX2NoYW5nZXNfVSksCiAgICAgIGRhdGEuZnJhbWUoaXRlciA9IGl0LCB2YXJpYWJsZSA9ICJQIiwgdmFsdWUgPSBhYnNfY2hhbmdlc19QKSkKICAgIAogICAgaWYgKHZlcmJvc2UpIHttZXNzYWdlKHNwcmludGYoIml0ZXIgJTNkOiByZWwoWik9JS4zZSwgcmVsKFUpPSUuM2UsIHJlbChQKT0lLjNlIiwgaXQsIHJlbF9jaGFuZ2VzX1osIHJlbF9jaGFuZ2VzX1UsIHJlbF9jaGFuZ2VzX1ApKX0KCiAgICAjIHVwZGF0ZSBzdG9yZWQgcHJldmlvdXMgaXRlcmF0ZXMKICAgIHpfcHJldiA8LSB6X25ldwogICAgVV9wcmV2IDwtIFVfbWF0CiAgICBQX3ByZXYgPC0gUF9tYXQKCiAgICAjIGNvbnZlcmdlbmNlIGNoZWNrOiByZXF1aXJlIGFsbCByZWxfY2hhbmdlcyA8PSByZXNwZWN0aXZlIHRvbAogICAgY29uZF9aIDwtIHJlbF9jaGFuZ2VzX1ogPD0gdG9sX2xpc3QkWgogICAgY29uZF9VIDwtIHJlbF9jaGFuZ2VzX1UgPD0gdG9sX2xpc3QkVQogICAgY29uZF9QIDwtIHJlbF9jaGFuZ2VzX1AgPD0gdG9sX2xpc3QkUAoKICAgIGlmICgoY29uZF9aICYmIGNvbmRfVSAmJiBjb25kX1ApIHx8IGl0ID49IG1heGl0KSB7CiAgICAgIGNvbnZlcmdlZCA8LSAoY29uZF9aICYmIGNvbmRfVSAmJiBjb25kX1ApCiAgICAgIGJyZWFrCiAgICB9CiAgfQoKICBpZiAodmVyYm9zZSAmJiAhY29udmVyZ2VkKSB7CiAgICBtZXNzYWdlKHNwcmludGYoCiAgICAgICJTdG9wcGVkIGF0IG1heGl0PSVkOyByZWxfY2hhbmdlczogWj0lLjNlICh0b2wgJS4zZSksIFU9JS4zZSAodG9sICUuM2UpLCBQPSUuM2UgKHRvbCAlLjNlKSIsCiAgICAgIGl0LCByZWxfY2hhbmdlc19aLCB0b2xfbGlzdCRaLCByZWxfY2hhbmdlc19VLCB0b2xfbGlzdCRVLCByZWxfY2hhbmdlc19QLCB0b2xfbGlzdCRQCiAgICApKQogIH0KCiAgcmV0dXJuKGxpc3QoVSA9IFVfbWF0LCAgIyBzb2x1dGlvbiBVCiAgICAgICAgICAgICAgWiA9IHpfbmV3LCAgIyBzb2x1dGlvbiB6CiAgICAgICAgICAgICAgUCA9IFBfbWF0LCAjIHNvbHV0aW9uIFAKICAgICAgICAgICAgICBpdGVyYXRpb25zID0gaXQsCiAgICAgICAgICAgICAgY29udmVyZ2VkID0gY29udmVyZ2VkLAogICAgICAgICAgICAgIHRvbF9saXN0ID0gdG9sX2xpc3QsCiAgICAgICAgICAgICAgcmVsX2hpc3RvcnkgPSByZWxfaGlzdG9yeSwKICAgICAgICAgICAgICBhYnNfaGlzdG9yeSA9IGFic19oaXN0b3J5KSkKfQoKcGxvdF9jb252ZXJnZW5jZV9oaXN0b3J5IDwtIGZ1bmN0aW9uKGhpc3RvcnlfZGYsIHRvbF9saXN0ID0gTlVMTCwgcmVsYXRpdmUgPSBUUlVFKSB7CgogIHAgPC0gZ2dwbG90KGhpc3RvcnlfZGYsIGFlcyh4ID0gaXRlciwgeSA9IHZhbHVlLCBjb2xvciA9IHZhcmlhYmxlKSkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMS41KSArCiAgICBzY2FsZV95X2xvZzEwKCkgKwogICAgbGFicygKICAgICAgdGl0bGUgPSBpZmVsc2UocmVsYXRpdmUsICJ8WF97aXRlcn0gLSBYX3tpdGVyLTF9fCAvIHxYX3tpdGVyfXwiLCAifFhfe2V4YWN0fSAtIFhfe2l0ZXJ9fCIpLAogICAgICB4ID0gIkl0ZXJhdGlvbiIsCiAgICAgIHkgPSAiRXJyb3IiLAogICAgICBjb2xvciA9ICJRdWFudGl0eSIKICAgICkgKwogICAgdGhlbWVfbWluaW1hbCgpCiAgCiAgIyBBZGQgdG9sZXJhbmNlIGxpbmVzIGlmIHByb3ZpZGVkCiAgaWYgKCFpcy5udWxsKHRvbF9saXN0KSkgewogICAgdG9sX2RmIDwtIGRhdGEuZnJhbWUoCiAgICAgIHZhcmlhYmxlID0gbmFtZXModG9sX2xpc3QpLAogICAgICB0b2wgPSB1bmxpc3QodG9sX2xpc3QpCiAgICApCiAgICBwIDwtIHAgKyBnZW9tX2hsaW5lKAogICAgICBkYXRhID0gdG9sX2RmLAogICAgICBhZXMoeWludGVyY2VwdCA9IHRvbCwgY29sb3IgPSB2YXJpYWJsZSksCiAgICAgIGxpbmV0eXBlID0gImRhc2hlZCIKICAgICkKICB9CiAgCiAgcmV0dXJuKHBsb3RseTo6Z2dwbG90bHkocCkpCn0KYGBgCgoKIyMgUGxvdHRpbmcgZnVuY3Rpb25zIHsjcGxvdHRpbmdfZnVuY3Rpb25zfQoKIyMjIEZ1bmN0aW9uIGBwbG90dGluZy5vcmRlcigpYAoKR2l2ZW4gYSB2ZWN0b3IgYHZgIGFuZCBhIGdyYXBoIG9iamVjdCBgZ3JhcGhgLCBmdW5jdGlvbiBgcGxvdHRpbmcub3JkZXIoKWAgb3JkZXJzIHRoZSBtZXNoIHZhbHVlcyBmb3IgcGxvdHRpbmcuCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBvcmRlciB0aGUgdmVydGljZXMgZm9yIHBsb3R0aW5nCnBsb3R0aW5nLm9yZGVyIDwtIGZ1bmN0aW9uKHYsIGdyYXBoKXsKICBlZGdlX251bWJlciA8LSBncmFwaCRtZXNoJFZ0RVssIDFdCiAgcG9zIDwtIHN1bShlZGdlX251bWJlciA9PSAxKSsxCiAgcmV0dXJuKGModlsxXSwgdlszOnBvc10sIHZbMl0sIHZbKHBvcysxKTpsZW5ndGgodildLCB2WzJdKSkKfQpgYGAKCiMjIyBGdW5jdGlvbiBgZ2xvYmFsLnNjZW5lLnNldHRlcigpYAoKR2l2ZW4gcmFuZ2VzIGZvciB0aGUgYHhgLCBgeWAsIGFuZCBgemAgYXhlcywgYW5kIGFuIG9wdGlvbmFsIGFzcGVjdCByYXRpbyBmb3IgdGhlIGB6YCBheGlzLCBmdW5jdGlvbiBgZ2xvYmFsLnNjZW5lLnNldHRlcigpYCBzZXRzIHRoZSBzY2VuZSBmb3IgM0QgcGxvdHMgc28gdGhhdCBhbGwgcGxvdHMgaGF2ZSB0aGUgc2FtZSBhc3BlY3QgcmF0aW8gYW5kIGNhbWVyYSBwb3NpdGlvbi4KCmBgYHtyfQojIEZ1bmN0aW9uIHRvIHNldCB0aGUgc2NlbmUgZm9yIDNEIHBsb3RzCmdsb2JhbC5zY2VuZS5zZXR0ZXIgPC0gZnVuY3Rpb24oeF9yYW5nZSwgeV9yYW5nZSwgel9yYW5nZSwgel9hc3BlY3RyYXRpbyA9IDQpIHsKICAKICByZXR1cm4obGlzdCh4YXhpcyA9IGxpc3QodGl0bGUgPSAieCIsIHJhbmdlID0geF9yYW5nZSksCiAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gInkiLCByYW5nZSA9IHlfcmFuZ2UpLAogICAgICAgICAgICAgIHpheGlzID0gbGlzdCh0aXRsZSA9ICJ6IiwgcmFuZ2UgPSB6X3JhbmdlKSwKICAgICAgICAgICAgICBhc3BlY3RyYXRpbyA9IGxpc3QoeCA9IDIqKDErMi9waSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gMiooMi9waSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB6ID0gel9hc3BlY3RyYXRpbyooMi9waSkpLAogICAgICAgICAgICAgIGNhbWVyYSA9IGxpc3QoZXllID0gbGlzdCh4ID0gKDErMi9waSkvMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSA0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeiA9IDIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VudGVyID0gbGlzdCh4ID0gKDErMi9waSkvMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeiA9IDApKSkpCn0KYGBgCgojIyMgRnVuY3Rpb24gYGdyYXBoLnBsb3R0ZXIuM2QoKWAKCkdpdmVuIGEgZ3JhcGggb2JqZWN0IGBncmFwaGAsIGEgc2VxdWVuY2Ugb2YgdGltZSBwb2ludHMgYHRpbWVfc2VxYCwgYW5kIG9uZSBvciBtb3JlIG1hdHJpY2VzIGAuLi5gIHJlcHJlc2VudGluZyBmdW5jdGlvbiB2YWx1ZXMgZGVmaW5lZCBvbiB0aGUgbWVzaCBvZiBgZ3JhcGhgIGF0IGVhY2ggdGltZSBpbiBgdGltZV9zZXFgLCB0aGUgYGdyYXBoLnBsb3R0ZXIuM2QoKWAgZnVuY3Rpb24gZ2VuZXJhdGVzIGFuIGludGVyYWN0aXZlIDNEIHZpc3VhbGl6YXRpb24gb2YgdGhlc2UgdmFsdWVzIG92ZXIgdGltZS4KCmBgYHtyfQojIEZ1bmN0aW9uIHRvIHBsb3QgaW4gM0QKZ3JhcGgucGxvdHRlci4zZCA8LSBmdW5jdGlvbihncmFwaCwgdGltZV9zZXEsIGZyYW1lX3ZhbF90b19kaXNwbGF5LCAuLi4pIHsKICBVX2xpc3QgPC0gbGlzdCguLi4pCiAgVV9uYW1lcyA8LSBzYXBwbHkoc3Vic3RpdHV0ZShsaXN0KC4uLikpWy0xXSwgZGVwYXJzZSkKCiAgIyBTcGF0aWFsIGNvb3JkaW5hdGVzCiAgeCA8LSBwbG90dGluZy5vcmRlcihncmFwaCRtZXNoJFZbLCAxXSwgZ3JhcGgpCiAgeSA8LSBwbG90dGluZy5vcmRlcihncmFwaCRtZXNoJFZbLCAyXSwgZ3JhcGgpCiAgd2VpZ2h0cyA8LSBncmFwaCRtZXNoJHdlaWdodHMKCiAgIyBBcHBseSBwbG90dGluZy5vcmRlciB0byBlYWNoIFUKICBVX2xpc3QgPC0gbGFwcGx5KFVfbGlzdCwgZnVuY3Rpb24oVSkgYXBwbHkoVSwgMiwgcGxvdHRpbmcub3JkZXIsIGdyYXBoID0gZ3JhcGgpKQogIG5fdmFycyA8LSBsZW5ndGgoVV9saXN0KQoKICAjIENyZWF0ZSBwbG90X2RhdGEgZnJhbWUgd2l0aCB0aW1lIGFuZCBwb3NpdGlvbiByZXBsaWNhdGVkCiAgbl90aW1lIDwtIG5jb2woVV9saXN0W1sxXV0pCiAgYmFzZV9kYXRhIDwtIGRhdGEuZnJhbWUoCiAgICB4ID0gcmVwKHgsIHRpbWVzID0gbl90aW1lKSwKICAgIHkgPSByZXAoeSwgdGltZXMgPSBuX3RpbWUpLAogICAgdGhlX2dyYXBoID0gMCwKICAgIGZyYW1lID0gcmVwKHRpbWVfc2VxLCBlYWNoID0gbGVuZ3RoKHgpKQogICkKCiAgIyBBZGQgVSBjb2x1bW5zIHRvIHBsb3RfZGF0YQogIGZvciAoaSBpbiBzZXFfYWxvbmcoVV9saXN0KSkgewogICAgYmFzZV9kYXRhW1twYXN0ZTAoInUiLCBpKV1dIDwtIGFzLnZlY3RvcihVX2xpc3RbW2ldXSkKICB9CgogIHBsb3RfZGF0YSA8LSBiYXNlX2RhdGEKCiAgIyBHZW5lcmF0ZSB2ZXJ0aWNhbCBsaW5lcwogIHZlcnRpY2FsX2xpbmVzX2xpc3QgPC0gbGFwcGx5KHNlcV9hbG9uZyhVX2xpc3QpLCBmdW5jdGlvbihpKSB7CiAgICBkby5jYWxsKHJiaW5kLCBsYXBwbHkodGltZV9zZXEsIGZ1bmN0aW9uKHQpIHsKICAgICAgaWR4IDwtIHdoaWNoKHBsb3RfZGF0YSRmcmFtZSA9PSB0KQogICAgICB6X3ZhbHMgPC0gcGxvdF9kYXRhW1twYXN0ZTAoInUiLCBpKV1dW2lkeF0KICAgICAgZGF0YS5mcmFtZSgKICAgICAgICB4ID0gcmVwKHBsb3RfZGF0YSR4W2lkeF0sIGVhY2ggPSAzKSwKICAgICAgICB5ID0gcmVwKHBsb3RfZGF0YSR5W2lkeF0sIGVhY2ggPSAzKSwKICAgICAgICB6ID0gYXMudmVjdG9yKHQoY2JpbmQoMCwgel92YWxzLCBOQSkpKSwKICAgICAgICBmcmFtZSA9IHJlcCh0LCBlYWNoID0gbGVuZ3RoKGlkeCkgKiAzKQogICAgICApCiAgICB9KSkKICB9KQoKICAjIFNldCBheGlzIHJhbmdlcwogIHpfcmFuZ2UgPC0gcmFuZ2UodW5saXN0KFVfbGlzdCkpCiAgeF9yYW5nZSA8LSByYW5nZSh4KQogIHlfcmFuZ2UgPC0gcmFuZ2UoeSkKCiAgIyBDcmVhdGUgcGxvdAogIHAgPC0gcGxvdF9seShwbG90X2RhdGEsIGZyYW1lID0gfmZyYW1lKSAlPiUKICAgIGFkZF90cmFjZSh4ID0gfngsIHkgPSB+eSwgeiA9IH50aGVfZ3JhcGgsIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgICAgICAgICAgbmFtZSA9ICIiLCBzaG93bGVnZW5kID0gRkFMU0UsCiAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmxhY2siLCB3aWR0aCA9IDMpKQoKICAjIEFkZCB0cmFjZXMgZm9yIGVhY2ggdmFyaWFibGUKICBjb2xvcnMgPC0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKG1pbihuX3ZhcnMsIDgpLCAiU2V0MSIpCiAgZm9yIChpIGluIHNlcV9hbG9uZyhVX2xpc3QpKSB7CiAgICBwIDwtIGFkZF90cmFjZShwLAogICAgICB4ID0gfngsIHkgPSB+eSwgeiA9IGFzLmZvcm11bGEocGFzdGUwKCJ+dSIsIGkpKSwKICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwgbmFtZSA9IFVfbmFtZXNbaV0sCiAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gY29sb3JzW2ldLCB3aWR0aCA9IDMpKQogIH0KCiAgIyBBZGQgdmVydGljYWwgbGluZXMKICBmb3IgKGkgaW4gc2VxX2Fsb25nKHZlcnRpY2FsX2xpbmVzX2xpc3QpKSB7CiAgICBwIDwtIGFkZF90cmFjZShwLAogICAgICBkYXRhID0gdmVydGljYWxfbGluZXNfbGlzdFtbaV1dLAogICAgICB4ID0gfngsIHkgPSB+eSwgeiA9IH56LCBmcmFtZSA9IH5mcmFtZSwKICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiZ3JheSIsIHdpZHRoID0gMC41KSwKICAgICAgbmFtZSA9ICJWZXJ0aWNhbCBsaW5lcyIsCiAgICAgIHNob3dsZWdlbmQgPSBGQUxTRSkKICB9CiAgZnJhbWVfbmFtZSA8LSBkZXBhcnNlKHN1YnN0aXR1dGUoZnJhbWVfdmFsX3RvX2Rpc3BsYXkpKQogICMgTGF5b3V0IGFuZCBhbmltYXRpb24gY29udHJvbHMKICBwIDwtIHAgJT4lCiAgICBsYXlvdXQoCiAgICAgIHNjZW5lID0gZ2xvYmFsLnNjZW5lLnNldHRlcih4X3JhbmdlLCB5X3JhbmdlLCB6X3JhbmdlKSwKICAgICAgdXBkYXRlbWVudXMgPSBsaXN0KGxpc3QodHlwZSA9ICJidXR0b25zIiwgc2hvd2FjdGl2ZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidXR0b25zID0gbGlzdCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KGxhYmVsID0gIlBsYXkiLCBtZXRob2QgPSAiYW5pbWF0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmdzID0gbGlzdChOVUxMLCBsaXN0KGZyYW1lID0gbGlzdChkdXJhdGlvbiA9IDIwMDAgLyBsZW5ndGgodGltZV9zZXEpLCByZWRyYXcgPSBUUlVFKSwgZnJvbWN1cnJlbnQgPSBUUlVFKSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QobGFiZWwgPSAiUGF1c2UiLCBtZXRob2QgPSAiYW5pbWF0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmdzID0gbGlzdChOVUxMLCBsaXN0KG1vZGUgPSAiaW1tZWRpYXRlIiwgZnJhbWUgPSBsaXN0KGR1cmF0aW9uID0gMCksIHJlZHJhdyA9IEZBTFNFKSkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgKSksCiAgICAgIHRpdGxlID0gcGFzdGUwKGZyYW1lX25hbWUsIjogIiwgZm9ybWF0QyhmcmFtZV92YWxfdG9fZGlzcGxheVsxXSwgZm9ybWF0ID0gImYiLCBkaWdpdHMgPSA0KSkKICAgICkgJT4lCiAgICBwbG90bHlfYnVpbGQoKQoKICBmb3IgKGkgaW4gc2VxX2Fsb25nKHAkeCRmcmFtZXMpKSB7CiAgICBwJHgkZnJhbWVzW1tpXV0kbGF5b3V0IDwtIGxpc3QodGl0bGUgPSBwYXN0ZTAoZnJhbWVfbmFtZSwiOiAiLCBmb3JtYXRDKGZyYW1lX3ZhbF90b19kaXNwbGF5W2ldLCBmb3JtYXQgPSAiZiIsIGRpZ2l0cyA9IDQpKSkKICB9CgogIHJldHVybihwKQp9CmBgYAoKCiMjIyBGdW5jdGlvbiBgZXJyb3IuYXQuZWFjaC50aW1lLnBsb3R0ZXIoKWAKCkdpdmVuIGEgZ3JhcGggb2JqZWN0IGBncmFwaGAsIGEgbWF0cml4IGBVX3RydWVgIG9mIHRydWUgdmFsdWVzLCBhIG1hdHJpeCBgVV9hcHByb3hgIG9mIGFwcHJveGltYXRlZCB2YWx1ZXMsIGEgc2VxdWVuY2Ugb2YgdGltZSBwb2ludHMgYHRpbWVfc2VxYCwgYW5kIGEgdGltZSBzdGVwIGB0aW1lX3N0ZXBgLCBmdW5jdGlvbiBgZXJyb3IuYXQuZWFjaC50aW1lLnBsb3R0ZXIoKWAgY29tcHV0ZXMgdGhlIGVycm9yIGF0IGVhY2ggdGltZSBzdGVwIGFuZCBnZW5lcmF0ZXMgYSBwbG90IHNob3dpbmcgdGhlIGVycm9yIG92ZXIgdGltZS4KCmBgYHtyfQojIEZ1bmN0aW9uIHRvIHBsb3QgdGhlIGVycm9yIGF0IGVhY2ggdGltZSBzdGVwCmVycm9yLmF0LmVhY2gudGltZS5wbG90dGVyIDwtIGZ1bmN0aW9uKGdyYXBoLCBVX3RydWUsIFVfYXBwcm94LCB0aW1lX3NlcSwgdGltZV9zdGVwKSB7CiAgd2VpZ2h0cyA8LSBncmFwaCRtZXNoJHdlaWdodHMKICBlcnJvcl9hdF9lYWNoX3RpbWUgPC0gdCh3ZWlnaHRzKSAlKiUgKFVfdHJ1ZSAtIFVfYXBwcm94KV4yCiAgZXJyb3IgPC0gc3FydChhcy5kb3VibGUodCh3ZWlnaHRzKSAlKiUgKFVfdHJ1ZSAtIFVfYXBwcm94KV4yICUqJSByZXAodGltZV9zdGVwLCBuY29sKFVfdHJ1ZSkpKSkKICBwIDwtIHBsb3RfbHkoKSAlPiUgCiAgYWRkX3RyYWNlKAogIHggPSB+dGltZV9zZXEsIHkgPSB+ZXJyb3JfYXRfZWFjaF90aW1lLCB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLAogIGxpbmUgPSBsaXN0KGNvbG9yID0gJ2JsdWUnLCB3aWR0aCA9IDIpLAogIG1hcmtlciA9IGxpc3QoY29sb3IgPSAnYmx1ZScsIHNpemUgPSA0KSwKICBuYW1lID0gIiIsCiAgc2hvd2xlZ2VuZCA9IFRSVUUKKSAlPiUgCiAgbGF5b3V0KAogIHRpdGxlID0gcGFzdGUwKCJFcnJvciBhdCBFYWNoIFRpbWUgU3RlcCAoVG90YWwgZXJyb3IgPSAiLCBmb3JtYXRDKGVycm9yLCBmb3JtYXQgPSAiZiIsIGRpZ2l0cyA9IDkpLCAiKSIpLAogIHhheGlzID0gbGlzdCh0aXRsZSA9ICJ0IiksCiAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkVycm9yIiksCiAgbGVnZW5kID0gbGlzdCh4ID0gMC4xLCB5ID0gMC45KQopCiAgcmV0dXJuKHApCn0KYGBgCgojIyMgRnVuY3Rpb24gYGdyYXBoLnBsb3R0ZXIuM2QuY29tcGFyZXIoKWAKCkdpdmVuIGEgZ3JhcGggb2JqZWN0IGBncmFwaGAsIG1hdHJpY2VzIGBVX3RydWVgIGFuZCBgVV9hcHByb3hgIHJlcHJlc2VudGluZyB0cnVlIGFuZCBhcHByb3hpbWF0ZWQgdmFsdWVzLCBhbmQgYSBzZXF1ZW5jZSBvZiB0aW1lIHBvaW50cyBgdGltZV9zZXFgLCBmdW5jdGlvbiBgZ3JhcGgucGxvdHRlci4zZC5jb21wYXJlcigpYCBnZW5lcmF0ZXMgYSAzRCBwbG90IGNvbXBhcmluZyB0aGUgdHJ1ZSBhbmQgYXBwcm94aW1hdGVkIHZhbHVlcyBvdmVyIHRpbWUsIHdpdGggY29sb3ItY29kZWQgdHJhY2VzIGZvciBlYWNoIHRpbWUgcG9pbnQuCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBwbG90IHRoZSAzRCBjb21wYXJpc29uIG9mIFVfdHJ1ZSBhbmQgVV9hcHByb3gKZ3JhcGgucGxvdHRlci4zZC5jb21wYXJlciA8LSBmdW5jdGlvbihncmFwaCwgVV90cnVlLCBVX2FwcHJveCwgdGltZV9zZXEpIHsKICB4IDwtIGdyYXBoJG1lc2gkVlssIDFdOyB5IDwtIGdyYXBoJG1lc2gkVlssIDJdCiAgeCA8LSBwbG90dGluZy5vcmRlcih4LCBncmFwaCk7IHkgPC0gcGxvdHRpbmcub3JkZXIoeSwgZ3JhcGgpCgogIFVfdHJ1ZSA8LSBhcHBseShVX3RydWUsIDIsIHBsb3R0aW5nLm9yZGVyLCBncmFwaCA9IGdyYXBoKQogIFVfYXBwcm94IDwtIGFwcGx5KFVfYXBwcm94LCAyLCBwbG90dGluZy5vcmRlciwgZ3JhcGggPSBncmFwaCkKICBuX3RpbWVzIDwtIGxlbmd0aCh0aW1lX3NlcSkKICAKICB4X3JhbmdlIDwtIHJhbmdlKHgpOyB5X3JhbmdlIDwtIHJhbmdlKHkpOyB6X3JhbmdlIDwtIHJhbmdlKGMoVV90cnVlLCBVX2FwcHJveCkpCiAgCiAgIyBOb3JtYWxpemUgdGltZV9zZXEKICB0aW1lX25vcm1hbGl6ZWQgPC0gKHRpbWVfc2VxIC0gbWluKHRpbWVfc2VxKSkgLyAobWF4KHRpbWVfc2VxKSAtIG1pbih0aW1lX3NlcSkpCiAgYmx1ZXMgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJsaWdodGJsdWUiLCAiYmx1ZSIpKShuX3RpbWVzKQogIHJlZHMgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJtaXN0eXJvc2UiLCAicmVkIikpKG5fdGltZXMpCiAgCiAgIyBBY2N1cmF0ZSBjb2xvcnNjYWxlcwogIGNvbG9yc2NhbGVfZ3JlZW5zIDwtIE1hcChmdW5jdGlvbih0LCBjb2wpIGxpc3QodCwgY29sKSwgdGltZV9ub3JtYWxpemVkLCBibHVlcykKICBjb2xvcnNjYWxlX3JlZHMgPC0gTWFwKGZ1bmN0aW9uKHQsIGNvbCkgbGlzdCh0LCBjb2wpLCB0aW1lX25vcm1hbGl6ZWQsIHJlZHMpCiAgCiAgcCA8LSBwbG90X2x5KCkKICAKICAjIFN0YXRpYyBibGFjayBncmFwaCBzdHJ1Y3R1cmUKICBwIDwtIHAgJT4lCiAgICBhZGRfdHJhY2UoeCA9IHgsIHkgPSB5LCB6ID0gcmVwKDAsIGxlbmd0aCh4KSksCiAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibGFjayIsIHdpZHRoID0gNCksCiAgICAgICAgICAgICAgbmFtZSA9ICJHcmFwaCIsIHNob3dsZWdlbmQgPSBGQUxTRSkKICAKICAjIFVfdHJ1ZSB0cmFjZXMgKGdyZWVuKQogIGZvciAoaSBpbiBzZXFfbGVuKG5fdGltZXMpKSB7CiAgICB6IDwtIFVfdHJ1ZVssIGldCiAgICBwIDwtIGFkZF90cmFjZSgKICAgICAgcCwKICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLAogICAgICBtb2RlID0gImxpbmVzIiwKICAgICAgeCA9IHgsIHkgPSB5LCB6ID0geiwKICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSBibHVlc1tpXSwgd2lkdGggPSA0KSwKICAgICAgc2hvd2xlZ2VuZCA9IEZBTFNFLAogICAgICBzY2VuZSA9ICJzY2VuZSIKICAgICkKICB9CiAgCiAgIyBVX2FwcHJveCB0cmFjZXMgKGRhc2hlZCByZWQpCiAgZm9yIChpIGluIHNlcV9sZW4obl90aW1lcykpIHsKICAgIHogPC0gVV9hcHByb3hbLCBpXQogICAgcCA8LSBhZGRfdHJhY2UoCiAgICAgIHAsCiAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwKICAgICAgbW9kZSA9ICJsaW5lcyIsCiAgICAgIHggPSB4LCB5ID0geSwgeiA9IHosCiAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gcmVkc1tpXSwgd2lkdGggPSA0LCBkYXNoID0gImRvdCIpLAogICAgICBzaG93bGVnZW5kID0gRkFMU0UsCiAgICAgIHNjZW5lID0gInNjZW5lIgogICAgKQogIH0KICAKICAjIER1bW15IGdyZWVuIGNvbG9yYmFyIChUcnVlKSDigJMgd2l0aCB0aWNrcwogIHAgPC0gYWRkX3RyYWNlKAogICAgcCwKICAgIHR5cGUgPSAiaGVhdG1hcCIsCiAgICB6ID0gbWF0cml4KHRpbWVfc2VxLCBucm93ID0gMSksCiAgICBzaG93c2NhbGUgPSBUUlVFLAogICAgY29sb3JzY2FsZSA9IGNvbG9yc2NhbGVfZ3JlZW5zLAogICAgY29sb3JiYXIgPSBsaXN0KAogICAgICB0aXRsZSA9IGxpc3QoZm9udCA9IGxpc3Qoc2l6ZSA9IDEyLCBjb2xvciA9ICJibGFjayIpLCB0ZXh0ID0gIlRpbWUiLCBzaWRlID0gInRvcCIpLAogICAgICBsZW4gPSAwLjksCiAgICAgIHRoaWNrbmVzcyA9IDE1LAogICAgICB4ID0gMS4wMiwKICAgICAgeGFuY2hvciA9ICJsZWZ0IiwKICAgICAgeSA9IDAuNSwKICAgICAgeWFuY2hvciA9ICJtaWRkbGUiLAogICAgICB0aWNrdmFscyA9IE5VTEwsICAgIyBoaWRlIHRpY2sgdmFsdWVzCiAgICAgIHRpY2t0ZXh0ID0gTlVMTCwKICAgICAgdGlja3MgPSAiIiAgICAgICAgICMgYWxzbyBoaWRlcyB0aWNrIG1hcmtzCiAgICApLAogICAgeCA9IG1hdHJpeCh0aW1lX3NlcSwgbnJvdyA9IDEpLAogICAgeSA9IG1hdHJpeCgxLCBucm93ID0gMSksCiAgICBob3ZlcmluZm8gPSAic2tpcCIsCiAgICBvcGFjaXR5ID0gMAogICkKCiMgRHVtbXkgcmVkIGNvbG9yYmFyIChBcHByb3gpIOKAkyBubyB0aWNrcwogIHAgPC0gYWRkX3RyYWNlKAogICAgcCwKICAgIHR5cGUgPSAiaGVhdG1hcCIsCiAgICB6ID0gbWF0cml4KHRpbWVfc2VxLCBucm93ID0gMSksCiAgICBzaG93c2NhbGUgPSBUUlVFLAogICAgY29sb3JzY2FsZSA9IGNvbG9yc2NhbGVfcmVkcywKICAgIGNvbG9yYmFyID0gbGlzdCgKICAgICAgdGl0bGUgPSBsaXN0KGZvbnQgPSBsaXN0KHNpemUgPSAxMiwgY29sb3IgPSAiYmxhY2siKSwgdGV4dCA9ICIuIiwgc2lkZSA9ICJ0b3AiKSwKICAgICAgbGVuID0gMC45LAogICAgICB0aGlja25lc3MgPSAxNSwKICAgICAgeCA9IDEuMDUsCiAgICAgIHhhbmNob3IgPSAibGVmdCIsCiAgICAgIHkgPSAwLjUsCiAgICAgIHlhbmNob3IgPSAibWlkZGxlIgogICAgKSwKICAgIHggPSBtYXRyaXgodGltZV9zZXEsIG5yb3cgPSAxKSwKICAgIHkgPSBtYXRyaXgoMSwgbnJvdyA9IDEpLAogICAgaG92ZXJpbmZvID0gInNraXAiLAogICAgb3BhY2l0eSA9IDAKICApCiAgcCA8LSBwICU+JQogICAgYWRkX3RyYWNlKHggPSB4LCB5ID0geSwgeiA9IHJlcCgwLCBsZW5ndGgoeCkpLAogICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmxhY2siLCB3aWR0aCA9IDQpLAogICAgICAgICAgICAgIG5hbWUgPSAiR3JhcGgiLCBzaG93bGVnZW5kID0gRkFMU0UpCiAgcCA8LSBsYXlvdXQocCwKICAgICAgICAgICAgc2NlbmUgPSBnbG9iYWwuc2NlbmUuc2V0dGVyKHhfcmFuZ2UsIHlfcmFuZ2UsIHpfcmFuZ2UpLAogICAgICAgICAgICB4YXhpcyA9IGxpc3QodmlzaWJsZSA9IEZBTFNFKSwKICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHZpc2libGUgPSBGQUxTRSksCiAgICAgICAgICAgIGFubm90YXRpb25zID0gbGlzdCgKICBsaXN0KAogICAgdGV4dCA9ICJFeGFjdCIsCiAgICB4ID0gMS4wNDUsCiAgICB5ID0gMC41LAogICAgeHJlZiA9ICJwYXBlciIsCiAgICB5cmVmID0gInBhcGVyIiwKICAgIHNob3dhcnJvdyA9IEZBTFNFLAogICAgZm9udCA9IGxpc3Qoc2l6ZSA9IDEyLCBjb2xvciA9ICJibGFjayIpLAogICAgdGV4dGFuZ2xlID0gLTkwCiAgKSwKICBsaXN0KAogICAgdGV4dCA9ICJBcHByb3giLAogICAgeCA9IDEuMDc1LAogICAgeSA9IDAuNSwKICAgIHhyZWYgPSAicGFwZXIiLAogICAgeXJlZiA9ICJwYXBlciIsCiAgICBzaG93YXJyb3cgPSBGQUxTRSwKICAgIGZvbnQgPSBsaXN0KHNpemUgPSAxMiwgY29sb3IgPSAiYmxhY2siKSwKICAgIHRleHRhbmdsZSA9IC05MAogICkKKQoKKQoKICAKICByZXR1cm4ocCkKfQpgYGAKCiMjIyBGdW5jdGlvbiBgZ3JhcGgucGxvdHRlci4zZC5zaW5nbGUoKWAKCkdpdmVuIGEgZ3JhcGggb2JqZWN0IGBncmFwaGAsIGEgbWF0cml4IGBVX3RydWVgIHJlcHJlc2VudGluZyB0cnVlIHZhbHVlcywgYW5kIGEgc2VxdWVuY2Ugb2YgdGltZSBwb2ludHMgYHRpbWVfc2VxYCwgZnVuY3Rpb24gYGdyYXBoLnBsb3R0ZXIuM2Quc2luZ2xlKClgIGdlbmVyYXRlcyBhIDNEIHBsb3Qgb2YgdGhlIHRydWUgdmFsdWVzIG92ZXIgdGltZSwgd2l0aCBjb2xvci1jb2RlZCB0cmFjZXMgZm9yIGVhY2ggdGltZSBwb2ludC4KCmBgYHtyfQojIEZ1bmN0aW9uIHRvIHBsb3QgYSBzaW5nbGUgM0QgbGluZSBmb3IgCmdyYXBoLnBsb3R0ZXIuM2Quc2luZ2xlIDwtIGZ1bmN0aW9uKGdyYXBoLCBVX3RydWUsIHRpbWVfc2VxKSB7CiAgeCA8LSBncmFwaCRtZXNoJFZbLCAxXTsgeSA8LSBncmFwaCRtZXNoJFZbLCAyXQogIHggPC0gcGxvdHRpbmcub3JkZXIoeCwgZ3JhcGgpOyB5IDwtIHBsb3R0aW5nLm9yZGVyKHksIGdyYXBoKQoKICBVX3RydWUgPC0gYXBwbHkoVV90cnVlLCAyLCBwbG90dGluZy5vcmRlciwgZ3JhcGggPSBncmFwaCkKICBuX3RpbWVzIDwtIGxlbmd0aCh0aW1lX3NlcSkKICAKICB4X3JhbmdlIDwtIHJhbmdlKHgpOyB5X3JhbmdlIDwtIHJhbmdlKHkpOyB6X3JhbmdlIDwtIHJhbmdlKFVfdHJ1ZSkKICB6X3JhbmdlWzFdIDwtIHpfcmFuZ2VbMV0gLSAxMF4tNgogIHZpcmlkaXNfY29sb3JzIDwtIHZpcmlkaXNMaXRlOjp2aXJpZGlzKDEwMCkKICAKICAjIE5vcm1hbGl6ZSB0aW1lX3NlcQogIHRpbWVfbm9ybWFsaXplZCA8LSAodGltZV9zZXEgLSBtaW4odGltZV9zZXEpKSAvIChtYXgodGltZV9zZXEpIC0gbWluKHRpbWVfc2VxKSkKICAjZ3JlZW5zIDwtIGNvbG9yUmFtcFBhbGV0dGUoYygicGFsZWdyZWVuIiwgImRhcmtncmVlbiIpKShuX3RpbWVzKQogIGdyZWVucyA8LSBjb2xvclJhbXBQYWxldHRlKGModmlyaWRpc19jb2xvcnNbMV0sIHZpcmlkaXNfY29sb3JzWzUwXSwgIHZpcmlkaXNfY29sb3JzWzEwMF0pKShuX3RpbWVzKQogICMgQWNjdXJhdGUgY29sb3JzY2FsZXMKICBjb2xvcnNjYWxlX2dyZWVucyA8LSBNYXAoZnVuY3Rpb24odCwgY29sKSBsaXN0KHQsIGNvbCksIHRpbWVfbm9ybWFsaXplZCwgZ3JlZW5zKQogIAogIHAgPC0gcGxvdF9seSgpCiAgCiAgIyBBZGQgdGhlIDNEIGxpbmVzIHdpdGggZmFkaW5nIGdyZWVuIGNvbG9yCiAgZm9yIChpIGluIHNlcV9sZW4obl90aW1lcykpIHsKICAgIHogPC0gVV90cnVlWywgaV0KICAgIAogICAgcCA8LSBhZGRfdHJhY2UoCiAgICAgIHAsCiAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwKICAgICAgbW9kZSA9ICJsaW5lcyIsCiAgICAgIHggPSB4LAogICAgICB5ID0geSwKICAgICAgeiA9IHosCiAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gZ3JlZW5zW2ldLCB3aWR0aCA9IDIpLAogICAgICBzaG93bGVnZW5kID0gRkFMU0UsCiAgICAgIHNjZW5lID0gInNjZW5lIgogICAgKQogIH0KICBwIDwtIHAgJT4lCiAgICBhZGRfdHJhY2UoeCA9IHgsIHkgPSB5LCB6ID0gcmVwKDAsIGxlbmd0aCh4KSksCiAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibGFjayIsIHdpZHRoID0gNSksCiAgICAgICAgICAgICAgbmFtZSA9ICJHcmFwaCIsIHNob3dsZWdlbmQgPSBGQUxTRSkKICAjIEFkZCBkdW1teSBoZWF0bWFwIHRvIHNob3cgY29sb3JiYXIgKG5vdCBwYXJ0IG9mIHNjZW5lKQogIHAgPC0gYWRkX3RyYWNlKAogICAgcCwKICAgIHR5cGUgPSAiaGVhdG1hcCIsCiAgICB6ID0gbWF0cml4KHRpbWVfc2VxLCBucm93ID0gMSksCiAgICBzaG93c2NhbGUgPSBUUlVFLAogICAgY29sb3JzY2FsZSA9IGNvbG9yc2NhbGVfZ3JlZW5zLAogICAgY29sb3JiYXIgPSBsaXN0KAogICAgdGl0bGUgPSBsaXN0KGZvbnQgPSBsaXN0KHNpemUgPSAxMiwgY29sb3IgPSAiYmxhY2siKSwgdGV4dCA9ICJUaW1lIiwgc2lkZSA9ICJ0b3AiKSwKICAgIGxlbiA9IDAuOSwgICAgICAgICAjIGhlaWdodCAoMCB0byAxKQogICAgdGhpY2tuZXNzID0gMTUsICAgICAjIHdpZHRoIGluIHBpeGVscwogICAgeCA9IDEuMDIsICAgICAgICAgICAjIHNoaWZ0IGl0IHNsaWdodGx5IHJpZ2h0IG9mIHRoZSBwbG90CiAgICB4YW5jaG9yID0gImxlZnQiLAogICAgeSA9IDAuNSwKICAgIHlhbmNob3IgPSAibWlkZGxlIiksCiAgICB4ID0gbWF0cml4KHRpbWVfc2VxLCBucm93ID0gMSksCiAgICB5ID0gbWF0cml4KDEsIG5yb3cgPSAxKSwKICAgIGhvdmVyaW5mbyA9ICJza2lwIiwKICAgIG9wYWNpdHkgPSAwCiAgKQogIAogIHAgPC0gbGF5b3V0KHAsCiAgICAgICAgICAgICAgc2NlbmUgPSBnbG9iYWwuc2NlbmUuc2V0dGVyKHhfcmFuZ2UsIHlfcmFuZ2UsIHpfcmFuZ2UpLAogICAgICAgICAgICAgIHhheGlzID0gbGlzdCh2aXNpYmxlID0gRkFMU0UpLAogICAgICAgICAgICAgIHlheGlzID0gbGlzdCh2aXNpYmxlID0gRkFMU0UpCiAgKQogIAogIHJldHVybihwKQp9CmBgYAoKIyMjIEZ1bmN0aW9uIGBlcnJvci5jb252ZXJnZW5jZS5wbG90dGVyKClgCgpgYGB7cn0KIyBGdW5jdGlvbiB0byBwbG90IHRoZSBlcnJvciBjb252ZXJnZW5jZQplcnJvci5jb252ZXJnZW5jZS5wbG90dGVyIDwtIGZ1bmN0aW9uKHhfYXhpc192ZWN0b3IsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhX3ZlY3RvciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXJyb3JzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVvcmV0aWNhbF9yYXRlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2JzZXJ2ZWRfcmF0ZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZV9lcXVhdGlvbl9mdW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlnX3RpdGxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHhfYXhpc19sYWJlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcHBseV9zcXJ0ID0gRkFMU0UpIHsKICAKICB4X3ZlYyA8LSBpZiAoYXBwbHlfc3FydCkgc3FydCh4X2F4aXNfdmVjdG9yKSBlbHNlIHhfYXhpc192ZWN0b3IKICAKICBndWlkaW5nX2xpbmVzIDwtIGNvbXB1dGVfZ3VpZGluZ19saW5lcyh4X2F4aXNfdmVjdG9yID0geF92ZWMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVycm9ycyA9IGVycm9ycywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlb3JldGljYWxfcmF0ZXMgPSB0aGVvcmV0aWNhbF9yYXRlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZV9lcXVhdGlvbl9mdW4gPSBsaW5lX2VxdWF0aW9uX2Z1bikKICAKICBkZWZhdWx0X2NvbG9ycyA8LSBzY2FsZXM6Omh1ZV9wYWwoKShsZW5ndGgoYWxwaGFfdmVjdG9yKSkKICAKICBwbG90X2xpbmVzIDwtIGxhcHBseSgxOm5jb2woZ3VpZGluZ19saW5lcyksIGZ1bmN0aW9uKGkpIHsKICAgIGdlb21fbGluZSgKICAgICAgZGF0YSA9IGRhdGEuZnJhbWUoeCA9IHhfdmVjLCB5ID0gZ3VpZGluZ19saW5lc1ssIGldKSwKICAgICAgYWVzKHggPSB4LCB5ID0geSksCiAgICAgIGNvbG9yID0gZGVmYXVsdF9jb2xvcnNbaV0sCiAgICAgIGxpbmV0eXBlID0gImRhc2hlZCIsCiAgICAgIHNob3cubGVnZW5kID0gRkFMU0UKICAgICkKICB9KQogIAogIGRmIDwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoeF92ZWMsIGVycm9ycykpCiAgY29sbmFtZXMoZGYpIDwtIGMoInhfYXhpc192ZWN0b3IiLCBhbHBoYV92ZWN0b3IpCiAgZGZfbWVsdGVkIDwtIG1lbHQoZGYsIGlkLnZhcnMgPSAieF9heGlzX3ZlY3RvciIsIHZhcmlhYmxlLm5hbWUgPSAiY29sdW1uIiwgdmFsdWUubmFtZSA9ICJ2YWx1ZSIpCiAgCiAgY3VzdG9tX2xhYmVscyA8LSBwYXN0ZTAoZm9ybWF0QyhhbHBoYV92ZWN0b3IsIGZvcm1hdCA9ICJmIiwgZGlnaXRzID0gMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICIgfCAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXRDKHRoZW9yZXRpY2FsX3JhdGVzLCBmb3JtYXQgPSAiZiIsIGRpZ2l0cyA9IDQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAiIHwgIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgZm9ybWF0QyhvYnNlcnZlZF9yYXRlcywgZm9ybWF0ID0gImYiLCBkaWdpdHMgPSA0KSkKICAKICBkZl9tZWx0ZWQkY29sdW1uIDwtIGZhY3RvcihkZl9tZWx0ZWQkY29sdW1uLCBsZXZlbHMgPSBhbHBoYV92ZWN0b3IsIGxhYmVscyA9IGN1c3RvbV9sYWJlbHMpCgogIHAgPC0gZ2dwbG90KCkgKwogICAgZ2VvbV9saW5lKGRhdGEgPSBkZl9tZWx0ZWQsIGFlcyh4ID0geF9heGlzX3ZlY3RvciwgeSA9IHZhbHVlLCBjb2xvciA9IGNvbHVtbikpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGRmX21lbHRlZCwgYWVzKHggPSB4X2F4aXNfdmVjdG9yLCB5ID0gdmFsdWUsIGNvbG9yID0gY29sdW1uKSkgKwogICAgcGxvdF9saW5lcyArCiAgICBsYWJzKAogICAgICB0aXRsZSA9IGZpZ190aXRsZSwKICAgICAgeCA9IHhfYXhpc19sYWJlbCwKICAgICAgeSA9IGV4cHJlc3Npb24oRXJyb3IpLAogICAgICBjb2xvciA9ICIgICAgICAgICAgzrEgIHwgdGhlbyAgfCBvYnMiCiAgICApICsKICAgIChpZiAoYXBwbHlfc3FydCkgewogICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0geF92ZWMsIGxhYmVscyA9IHJvdW5kKHhfYXhpc192ZWN0b3IsIDQpKQogICAgfSBlbHNlIHsKICAgICAgc2NhbGVfeF9sb2cxMChicmVha3MgPSB4X2F4aXNfdmVjdG9yLCBsYWJlbHMgPSByb3VuZCh4X2F4aXNfdmVjdG9yLCA0KSkKICAgIH0pICsKICAgIChpZiAoYXBwbHlfc3FydCkgewogICAgICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9nIiwgbGFiZWxzID0gc2NhbGVzOjpzY2llbnRpZmljX2Zvcm1hdCgpKQogICAgfSBlbHNlIHsKICAgICAgc2NhbGVfeV9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OnNjaWVudGlmaWNfZm9ybWF0KCkpCiAgICB9KSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiUGFsYXRpbm8iKSwKICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgICAgICAgbGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIsCiAgICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigwLCAwLCAwLCAwKSwKICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemUgPSAxOCwgZmFjZSA9ICJib2xkIikpCiAgCiAgcmV0dXJuKHApCn0KCmBgYAoKCmBgYHtyfQpncmFwaC5wbG90dGVyLjNkLnN0YXRpYyA8LSBmdW5jdGlvbihncmFwaCwgLi4uKSB7CiAgeCA8LSBwbG90dGluZy5vcmRlcihncmFwaCRtZXNoJFZbLCAxXSwgZ3JhcGgpCiAgeSA8LSBwbG90dGluZy5vcmRlcihncmFwaCRtZXNoJFZbLCAyXSwgZ3JhcGgpCgogIHpfbGlzdCA8LSBsaXN0KC4uLikKICB6X2xpc3QgPC0gbGFwcGx5KHpfbGlzdCwgZnVuY3Rpb24oeikgcGxvdHRpbmcub3JkZXIoeiwgZ3JhcGgpKQogIFVfbmFtZXMgPC0gc2FwcGx5KHN1YnN0aXR1dGUobGlzdCguLi4pKVstMV0sIGRlcGFyc2UpCgogICMgQXhpcyByYW5nZXMKICB6X3JhbmdlIDwtIHJhbmdlKHVubGlzdCh6X2xpc3QpKQogIHhfcmFuZ2UgPC0gcmFuZ2UoeCkKICB5X3JhbmdlIDwtIHJhbmdlKHkpCgogIHAgPC0gcGxvdF9seSgpCiAgY29sb3JzIDwtIFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbChtYXgobGVuZ3RoKHpfbGlzdCksIDMpLCAiU2V0MSIpCgogIGZvciAoaSBpbiBzZXFfYWxvbmcoel9saXN0KSkgewogICAgeiA8LSB6X2xpc3RbW2ldXQoKICAgICMgTWFpbiAzRCBjdXJ2ZQogICAgcCA8LSBhZGRfdHJhY2UoCiAgICAgIHAsCiAgICAgIHggPSB4LCB5ID0geSwgeiA9IHosCiAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gY29sb3JzW2ldLCB3aWR0aCA9IDMpLAogICAgICBuYW1lID0gVV9uYW1lc1tpXSwgc2hvd2xlZ2VuZCA9IFRSVUUKICAgICkKCiAgICAjIEVmZmljaWVudCB2ZXJ0aWNhbCBsaW5lczogb25lIHRyYWNlIHdpdGggYnJlYWtzIChOQSkKICAgIHhfdmVydCA8LSByZXAoeCwgZWFjaCA9IDMpCiAgICB5X3ZlcnQgPC0gcmVwKHksIGVhY2ggPSAzKQogICAgel92ZXJ0IDwtIHVubGlzdChsYXBwbHkoeiwgZnVuY3Rpb24oemopIGMoMCwgemosIE5BKSkpCgogICAgcCA8LSBhZGRfdHJhY2UoCiAgICAgIHAsCiAgICAgIHggPSB4X3ZlcnQsIHkgPSB5X3ZlcnQsIHogPSB6X3ZlcnQsCiAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImdyYXkiLCB3aWR0aCA9IDAuNSksCiAgICAgIHNob3dsZWdlbmQgPSBGQUxTRQogICAgKQogIH0KICBwIDwtIHAgJT4lIGFkZF90cmFjZSh4ID0geCwgeSA9IHksIHogPSB4KjAsIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmxhY2siLCB3aWR0aCA9IDMpLAogICAgICAgICAgICAgIG5hbWUgPSAidGhlZ3JhcGgiLCBzaG93bGVnZW5kID0gRkFMU0UpICU+JQogICAgbGF5b3V0KHNjZW5lID0gZ2xvYmFsLnNjZW5lLnNldHRlcih4X3JhbmdlLCB5X3JhbmdlLCB6X3JhbmdlKSkKICByZXR1cm4ocCkKfQoKYGBgCgojIyBSZWZlcmVuY2VzCgpgYGB7ciwgcHVybCA9IEZBTFNFfQpncmF0ZWZ1bDo6Y2l0ZV9wYWNrYWdlcyhvdXRwdXQgPSAicGFyYWdyYXBoIiwgb3V0LmRpciA9ICIuIikKYGBgCgoK