Modules in the context of High-Performance Computing (HPC) systems are packages that I can load and unload dynamically to configure my environment(s) with the necessary software. Here’s a brief explanation of each module listed:

Compiler Modules

  1. amd/aocc/4.1.0
    • AMD AOCC (AMD Optimizing C/C++ Compiler): This is a high-performance, production-quality code generation tool. It supports C, C++, and Fortran programming languages and is specifically optimized for AMD processors.
  2. gcc/11.2.0 and gcc/13.2.0
    • GCC (GNU Compiler Collection): These are widely-used compilers for C, C++, and Fortran. GCC 11.2.0 and GCC 13.2.0 are different versions, with the latter being the default (denoted by ‘D’) version in this environment. GCC is known for its portability and is a standard compiler on many Linux distributions.
  3. intel/oneapi/2023.2
    • Intel OneAPI: This is a comprehensive, cross-architecture programming toolkit. It includes compilers (like ICC for C/C++ and IFORT for Fortran), libraries, and analysis tools optimized for Intel processors. OneAPI is designed to help developers build high-performance applications.
  4. nvidia/nvhpc/23.7
    • NVIDIA HPC SDK: This includes compilers, libraries, and tools to develop applications for NVIDIA GPUs and CPUs. The NVHPC (formerly PGI) compiler suite supports C, C++, and Fortran programming languages and is optimized for CUDA and GPU-accelerated applications.

Application Modules

  1. amber/22
    • AMBER (Assisted Model Building with Energy Refinement): A suite of biomolecular simulation programs. It is used primarily for molecular dynamics simulations of proteins and nucleic acids.
  2. apptainer/1.1.9
    • Apptainer: Formerly known as Singularity, it is a container platform designed for HPC environments. It allows users to create and run containers that package up applications and their dependencies in a portable manner.
  3. conda
    • Conda: An open-source package management and environment management system. It allows you to create isolated environments and install packages from the Conda repository. It is particularly popular for managing Python environments and packages.
  4. gaussian/g16c02
    • Gaussian: Software used for computational chemistry. Gaussian 16 is a version of the Gaussian software suite, which performs electronic structure calculations. It is widely used in chemistry for predicting the energies, molecular structures, and vibrational frequencies of molecular systems.
  5. julia/1.9.2
    • Julia: A high-level, high-performance programming language for technical computing. It is designed for numerical and scientific computing, and version 1.9.2 is a specific release of this language.
  6. lammps/may22
    • LAMMPS (Large-scale Atomic/Molecular Massively Parallel Simulator): A classical molecular dynamics code that can model an ensemble of particles in a liquid, solid, or gaseous state. LAMMPS is widely used in materials science, chemistry, and physics.
  7. spack
    • Spack: A flexible package manager that supports multiple versions and configurations of software. It is particularly designed for HPC environments and allows users to easily install, manage, and swap software packages.

To use the NVIDIA NVHPC 23.7 compiler suite on HPC system:

Step 1: Load the NVHPC Module

Load the NVIDIA NVHPC module -sets up environment with the necessary paths and environment variables.

module load nvidia/nvhpc/23.7

Verify that the module has been loaded correctly by checking the currently loaded modules:

module list

Step 2: Compile my Code

Once loaded,use the NVHPC compilers to compile code. NVHPC includes several compilers:

Here are examples of how to compile C, C++, and Fortran code:

C Code Compilation

nvc -o my_program my_program.c

C++ Code Compilation

nvc++ -o my_program my_program.cpp

Fortran Code Compilation

nvfortran -o my_program my_program.f90

Step 3: Run my Program

After compiling code run the executable as I would any other program:

./my_program

Step 4: Using CUDA

If code utilizes CUDA for GPU acceleration, NVHPC supports CUDA directly. Here is an example of compiling a CUDA program:

nvc++ -o my_cuda_program my_cuda_program.cu

Example Workflow

Example workflow:

  1. Load the NVHPC module:

    module load nvidia/nvhpc/23.7
  2. Compile C program:

    nvc -o hello_world hello_world.c
  3. Run compiled program:

    ./hello_world

Additional Tools and Libraries

NVHPC also comes with additional tools and libraries, such as:

To access the documentation and additional help for NVHPC tools, can use the man command or refer to the online documentation provided by NVIDIA.

Example CUDA Program Compilation

Here’s a brief example of compiling a CUDA program:

hello_world.cu:

#include <stdio.h>

__global__ void helloFromGPU() {
    printf("Hello World from GPU!\n");
}

int main() {
    helloFromGPU<<<1, 1>>>();
    cudaDeviceSynchronize();
    return 0;
}

Compiling and Running:

  1. Compile:

    nvc++ -o hello_world hello_world.cu
  2. Run:

    ./hello_world

Creating a Quantum Generative Adversarial Network (QGAN) for anomaly detection in Geographic Information Systems (GIS) is an advanced task that involves integrating classical and quantum computing resources. Here’s a detailed guide on how to set up the environment and the tools I could use:

Step 1: Choose the Right Environment and Tools

  1. Quantum Computing SDK: Use IBM’s Qiskit or Google’s Cirq for quantum computing components.
  2. Classical Computing Environment: Use Python with libraries such as TensorFlow or PyTorch for classical machine learning components.
  3. GIS Tools: Use libraries like GeoPandas, GDAL, or other relevant GIS tools in Python.
  4. Compiler: You can use the NVIDIA NVHPC compiler for optimizing classical code, but Python environments usually use standard interpreters.

Step 2: Set Up the Classical Environment

First, ensure that I have Python and necessary packages installed. Using conda to create an isolated environment is recommended.

1. Install Anaconda or Miniconda

Download and install Anaconda or Miniconda from the official website.

2. Create a Conda Environment

conda create -n qgan_env python=3.9
conda activate qgan_env

3. Install Required Packages

conda install numpy pandas geopandas gdal matplotlib
pip install qiskit tensorflow torch torchvision

Step 3: Set Up Quantum Computing SDK

1. Install Qiskit

If using IBM’s Qiskit:

pip install qiskit

For Cirq (Google):

pip install cirq

Step 4: Integrate Classical and Quantum Components

Here’s an example of a workflow integrating classical and quantum components for a QGAN:

1. Classical GAN for GIS Data

Create a classical GAN for generating GIS data anomalies. Below is a simplified example using PyTorch:

import torch
import torch.nn as nn
import torch.optim as optim
import geopandas as gpd

# Example: Simple GAN components
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.main = nn.Sequential(
            nn.Linear(100, 256),
            nn.ReLU(),
            nn.Linear(256, 512),
            nn.ReLU(),
            nn.Linear(512, 1024),
            nn.Tanh()
        )
    
    def forward(self, input):
        return self.main(input)

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.main = nn.Sequential(
            nn.Linear(1024, 512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )
    
    def forward(self, input):
        return self.main(input)

# Initialize models
G = Generator()
D = Discriminator()

# Optimizers and loss function
optimizerG = optim.Adam(G.parameters(), lr=0.0002)
optimizerD = optim.Adam(D.parameters(), lr=0.0002)
criterion = nn.BCELoss()

# Training loop placeholder
for epoch in range(epochs):
    # Training code here
    pass

2. Integrate Quantum Component

Using Qiskit for quantum parts of QGAN:

from qiskit import QuantumCircuit, transpile, Aer, execute
from qiskit.visualization import plot_histogram

# Quantum generator
def quantum_generator():
    qc = QuantumCircuit(1, 1)
    qc.h(0)
    qc.measure(0, 0)
    return qc

# Simulate the quantum circuit
backend = Aer.get_backend('qasm_simulator')
qc = quantum_generator()
job = execute(qc, backend, shots=1000)
result = job.result()
counts = result.get_counts(qc)
print(counts)

plot_histogram(counts)

Step 5: Integrate Classical and Quantum Components in the QGAN

Combine classical and quantum components. Here’s a simplified pseudocode integration:

# Example of integrating quantum-generated data with classical GAN training
for epoch in range(epochs):
    for i, data in enumerate(dataloader):
        # Quantum data generation
        qc = quantum_generator()
        job = execute(qc, backend, shots=1)
        result = job.result()
        quantum_data = list(result.get_counts(qc).keys())[0]

        # Convert quantum data to input format
        quantum_input = torch.Tensor([float(bit) for bit in quantum_data])

        # Classical GAN training steps
        real_data = data[0]
        fake_data = G(quantum_input)

        # Update Discriminator
        D.zero_grad()
        real_output = D(real_data)
        fake_output = D(fake_data.detach())
        lossD = criterion(real_output, torch.ones_like(real_output)) + criterion(fake_output, torch.zeros_like(fake_output))
        lossD.backward()
        optimizerD.step()

        # Update Generator
        G.zero_grad()
        fake_output = D(fake_data)
        lossG = criterion(fake_output, torch.ones_like(fake_output))
        lossG.backward()
        optimizerG.step()

        # Print losses (optional)
        print(f'Epoch [{epoch}/{epochs}], Step [{i}/{len(dataloader)}], Loss D: {lossD.item()}, Loss G: {lossG.item()}')

Conclusion

  1. Set up the environment: Install necessary tools using conda and pip.
  2. Create classical and quantum models: Use PyTorch for classical GAN and Qiskit for quantum components.
  3. Integrate models: Combine quantum-generated data with classical GAN training.

Create a QGAN (Quantum Generative Adversarial Network) for anomaly detection in GIS (Geographic Information Systems) on the HPC system I need to leverage both quantum and classical computing resources effectively. Here is a step-by-step guide to set up my environment and compile/run my QGAN on the HPC system:

Step 1: Load Necessary Modules

First, load the modules necessary for environment. This includes Python, any required quantum computing SDKs, and other libraries.

module load gcc/13.2.0
module load nvidia/nvhpc/23.7
module load conda

Step 2: Create and Activate a Conda Environment

Use Conda to manage my Python environment, which will include the necessary libraries for both quantum and classical computing.

conda create -n qgan_env python=3.9
conda activate qgan_env

Step 3: Install Required Python Packages

With Conda environment activated, install the necessary Python packages.

# Install basic packages for GIS and quantum computing
conda install numpy pandas geopandas gdal matplotlib
pip install qiskit tensorflow torch torchvision

Step 4: Set Up the Quantum Computing Environment

Install Qiskit (IBM’s Quantum Computing SDK)

pip install qiskit

Step 5: Develop the QGAN

Create the QGAN by integrating both classical and quantum components. Below is an example setup.

Classical GAN for GIS Data (using PyTorch)

import torch
import torch.nn as nn
import torch.optim as optim
import geopandas as gpd
import numpy as np

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.main = nn.Sequential(
            nn.Linear(100, 256),
            nn.ReLU(),
            nn.Linear(256, 512),
            nn.ReLU(),
            nn.Linear(512, 1024),
            nn.Tanh()
        )
    
    def forward(self, input):
        return self.main(input)

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.main = nn.Sequential(
            nn.Linear(1024, 512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )
    
    def forward(self, input):
        return self.main(input)

# Initialize models
G = Generator()
D = Discriminator()

# Optimizers and loss function
optimizerG = optim.Adam(G.parameters(), lr=0.0002)
optimizerD = optim.Adam(D.parameters(), lr=0.0002)
criterion = nn.BCELoss()

# Dummy training loop placeholder
epochs = 100
for epoch in range(epochs):
    # Training code here
    pass

Integrate Quantum Component using Qiskit

from qiskit import QuantumCircuit, Aer, execute
from qiskit.visualization import plot_histogram

def quantum_generator():
    qc = QuantumCircuit(1, 1)
    qc.h(0)
    qc.measure(0, 0)
    return qc

backend = Aer.get_backend('qasm_simulator')
qc = quantum_generator()
job = execute(qc, backend, shots=1000)
result = job.result()
counts = result.get_counts(qc)
print(counts)

plot_histogram(counts)

Step 6: Integrate Classical and Quantum Components in the QGAN

Combine quantum-generated data with classical GAN training:

# Example of integrating quantum-generated data with classical GAN training
for epoch in range(epochs):
    for i, data in enumerate(dataloader):
        # Quantum data generation
        qc = quantum_generator()
        job = execute(qc, backend, shots=1)
        result = job.result()
        quantum_data = list(result.get_counts(qc).keys())[0]

        # Convert quantum data to input format
        quantum_input = torch.Tensor([float(bit) for bit in quantum_data])

        # Classical GAN training steps
        real_data = data[0]
        fake_data = G(quantum_input)

        # Update Discriminator
        D.zero_grad()
        real_output = D(real_data)
        fake_output = D(fake_data.detach())
        lossD = criterion(real_output, torch.ones_like(real_output)) + criterion(fake_output, torch.zeros_like(fake_output))
        lossD.backward()
        optimizerD.step()

        # Update Generator
        G.zero_grad()
        fake_output = D(fake_data)
        lossG = criterion(fake_output, torch.ones_like(fake_output))
        lossG.backward()
        optimizerG.step()

        # Print losses (optional)
        print(f'Epoch [{epoch}/{epochs}], Step [{i}/{len(dataloader)}], Loss D: {lossD.item()}, Loss G: {lossG.item()}')

Step 7: Run on the HPC System

After setting up the environment and writing code, you can submit job to the HPC system using a job scheduler like SLURM or PBS. Here is an example SLURM script:

#!/bin/bash
#SBATCH --job-name=qgan_gis
#SBATCH --output=qgan_gis_output.txt
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=4
#SBATCH --mem=16G
#SBATCH --time=24:00:00
#SBATCH --partition=gpu
#SBATCH --gpus=1

module load gcc/13.2.0
module load nvidia/nvhpc/23.7
module load conda

conda activate qgan_env

python my_qgan_script.py

Save this script as submit_job.sh and submit it to the job scheduler:

sbatch submit_job.sh

By following these steps, I should be able to create and run a QGAN for anomaly detection in GIS on my HPC system, utilizing both quantum and classical computing resources.

M3

The modules and compilers available on the HPC system (m3) provide various tools for scientific computing, development, and analysis. Here’s an explanation of each module and compiler listed:

Applications Modules

  1. R
    • Versions: 4.2.3, 4.3.0, 4.3.2, 4.4.0 (D)
    • R: A programming language and software environment used for statistical computing and graphics. It is widely used for data analysis and visualization.
  2. ampl/20231031
    • AMPL: A comprehensive and powerful algebraic modeling language for linear and nonlinear optimization problems.
  3. charmm/c47b2
    • CHARMM: A program for macromolecular dynamics and mechanics simulations, used for studying the behavior of molecular systems.
  4. dynare
    • Versions: 5.4, 5.5 (D)
    • Dynare: A software platform for handling a wide class of economic models, particularly those involving dynamic stochastic general equilibrium (DSGE) and overlapping generations (OLG) models.
  5. gaussian
    • Versions: g16c/haswell, g16c/zen3 (D)
    • Gaussian: Software for computational chemistry used for electronic structure modeling.
  6. julia
    • Versions: 1.8.5, 1.9.1 (D)
    • Julia: A high-level, high-performance programming language for technical computing.
  7. nbo
    • Versions: 7.0/i4, 7.0/i8 (D)
    • NBO (Natural Bond Orbital): Software for studying chemical bonding and electron density.
  8. snid/5.0
    • SNID (SuperNova Identification): A tool for classifying supernova spectra.
  9. vmd/1.9.3
    • VMD (Visual Molecular Dynamics): Software for molecular modeling and bioinformatics.
  10. ansys/electronics
    • Versions: 23.1, 23.2 (D)
    • ANSYS Electronics: Simulation software for electronics, electromagnetic, and thermal analysis.
  11. comsol/6.1
    • COMSOL: A multiphysics simulation software for modeling and simulating engineering problems.
  12. conda
    • Conda: An open-source package management and environment management system that allows users to create isolated environments and install packages.
  13. cplex/22.1.1
    • CPLEX: Optimization software for solving linear programming, mixed integer programming, and other types of optimization problems.
  14. eog/3.28.4
    • EOG (Eye of GNOME): An image viewer for the GNOME desktop environment.
  15. gedit/3.28.1
    • Gedit: A text editor for the GNOME desktop environment.
  16. gaussview/6.0.16
    • GaussView: A graphical interface for Gaussian software, aiding in the setup and analysis of computational chemistry calculations.
  17. mathematica
    • Versions: 13.3.0, r2024a (D)
    • Mathematica: A computational software used in scientific, engineering, mathematical fields, and other areas for computations and visualization.
  18. matlab
    • Versions: r2023a, r2024a (D)
    • MATLAB: A high-level programming language and environment used for numerical computing, data analysis, and algorithm development.
  19. orca/5.0.4
    • ORCA: An electronic structure software package for quantum chemistry calculations.
  20. q-chem
    • Versions: 5.2.2, 6.0.2 (D)
    • Q-Chem: A software package for computational chemistry, focusing on quantum chemical calculations.
  21. stata
    • Versions: mp-17, mp-18 (D)
    • Stata: A software for statistics and data science, used for data analysis, data management, and graphics.
  22. aimall/19.10.12
    • AIMAll: Software for analyzing molecular wave functions and electron density.
  23. apptainer/1.1.6
    • Apptainer (formerly Singularity): A container platform designed for HPC environments, allowing portable and reproducible software environments.
  24. blender/4.0.2
    • Blender: Open-source 3D modeling, animation, and rendering software.
  25. cfour
    • Versions: 2.1/mpi, 2.1/nompi (D)
    • CFOUR: A quantum chemistry program package for high-accuracy calculations.
  26. crystal
    • Versions: 23/1.0.1, 23/1.0.1-1 (D)
    • CRYSTAL: Software for computing the electronic structure of periodic systems.
  27. demon/6.2.2
    • deMon: A software for density functional theory (DFT) calculations.
  28. ds9/8.4.1
    • DS9: A tool for astronomical data visualization and analysis.
  29. gamess/2022.2
    • GAMESS (General Atomic and Molecular Electronic Structure System): A program for computational chemistry.
  30. guori
    • Versions: 10.0.1, 11.0.1 (D)
    • Gurobi: An optimization solver for mathematical programming.
  31. idl/8.0
    • IDL (Interactive Data Language): A programming language used for data analysis, visualization, and cross-platform application development.
  32. molden/6.9
    • Molden: A visualization package for molecular and electronic structure.
  33. molpro
    • Versions: 2022.3, 2024.1 (D)
    • Molpro: Software for high-accuracy electronic structure calculations.
  34. mojo/0.6.1
    • Mojo: A framework for creating machine learning pipelines.
  35. quantum_atk
    • Versions: 2022.03-SP1, 2022.12-SP1 (D)
    • QuantumATK: A software platform for atomic-scale modeling and simulation.
  36. sas/9.4m7
    • SAS: A software suite for advanced analytics, business intelligence, data management, and predictive analytics.
  37. synopsys/photonicsolutions/2021.09-3
    • Synopsys Photonic Solutions: Software tools for designing and simulating photonic and optoelectronic components and systems.
  38. tcad/2022
    • TCAD: Technology Computer-Aided Design software used for simulating semiconductor processing and devices.
  39. texlive/2023
    • TeX Live: A comprehensive TeX system for editing and publishing documents.
  40. vasp/5.4.4
    • VASP (Vienna Ab initio Simulation Package): A software for atomic-scale materials modeling.

Compiler Modules

  1. amd/4.0.0
    • AMD Optimizing C/C++ Compiler: Optimized for AMD processors, supporting C, C++, and Fortran programming languages.
  2. gcc
    • Versions: 6.3.0, 11.2.0 (D)
    • GCC (GNU Compiler Collection): A widely-used compiler supporting C, C++, and Fortran.
  3. intel
    • Versions: 2023.1, oneapi/2023.2
    • Intel Compilers: Optimized for Intel processors, supporting high-performance computing and applications.
  4. nvidia
    • Versions: 21.3, 23.5 (D)
    • NVIDIA HPC SDK: Compilers and tools for GPU-accelerated applications, supporting CUDA and other parallel programming models.
  5. rust/1.71.0
    • Rust: A systems programming language focused on safety, speed, and concurrency.

Usage Example

To use these modules on the HPC system, you need to load the required modules before compiling or running my code. Here’s an example of how to load a module and compile a program:

# Load the GCC compiler module
module load gcc/11.2.0

# Compile a C program
gcc -o my_program my_program.c

# Load the Python environment with Conda
module load conda
conda activate my_env

# Run a Python script
python my_script.py

project on m3

To develop a Quantum Generative Adversarial Network (QGAN) on the M3 HPC system for my project, you will need to integrate quantum computing with classical computing tools. Here’s a comprehensive step-by-step guide to help you get started:

Step 1: Set Up my Environment

1. Connect to the M3 HPC System

Use SSH to connect to the M3 HPC system.

ssh jmcphaul@m3login01

2. Load Necessary Modules

First, you need to load the necessary modules for my environment. This includes compilers, Python environments, and any relevant applications.

module load gcc/11.2.0
module load conda
module load nvidia/23.5

Step 2: Create and Activate a Conda Environment

Use Conda to create an isolated environment for my project, which will include the necessary Python packages for both classical and quantum computing.

conda create -n qgan_env python=3.9
conda activate qgan_env

Step 3: Install Required Python Packages

With my Conda environment activated, install the necessary Python packages for quantum computing, machine learning, and GIS applications.

# Install basic packages for GIS and quantum computing
conda install numpy pandas geopandas gdal matplotlib
pip install qiskit tensorflow torch torchvision

Step 4: Develop the QGAN

You will create the QGAN by integrating both classical and quantum components. Below is an example setup.

Classical GAN for GIS Data (using PyTorch)

Create and train a classical GAN for generating and analyzing GIS data.

GAN Components

import torch
import torch.nn as nn
import torch.optim as optim

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.main = nn.Sequential(
            nn.Linear(100, 256),
            nn.ReLU(),
            nn.Linear(256, 512),
            nn.ReLU(),
            nn.Linear(512, 1024),
            nn.Tanh()
        )
    
    def forward(self, input):
        return self.main(input)

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.main = nn.Sequential(
            nn.Linear(1024, 512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )
    
    def forward(self, input):
        return self.main(input)

# Initialize models
G = Generator()
D = Discriminator()

# Optimizers and loss function
optimizerG = optim.Adam(G.parameters(), lr=0.0002)
optimizerD = optim.Adam(D.parameters(), lr=0.0002)
criterion = nn.BCELoss()

# Dummy training loop placeholder
epochs = 100
for epoch in range(epochs):
    # Training code here
    pass

Integrate Quantum Component using Qiskit

Add the quantum component to my GAN using Qiskit for quantum data generation.

from qiskit import QuantumCircuit, Aer, execute
from qiskit.visualization import plot_histogram

def quantum_generator():
    qc = QuantumCircuit(1, 1)
    qc.h(0)
    qc.measure(0, 0)
    return qc

backend = Aer.get_backend('qasm_simulator')
qc = quantum_generator()
job = execute(qc, backend, shots=1000)
result = job.result()
counts = result.get_counts(qc)
print(counts)

plot_histogram(counts)

Step 5: Integrate Classical and Quantum Components in the QGAN

Combine quantum-generated data with classical GAN training:

import geopandas as gpd
import numpy as np

# Example of integrating quantum-generated data with classical GAN training
for epoch in range(epochs):
    for i, data in enumerate(dataloader):
        # Quantum data generation
        qc = quantum_generator()
        job = execute(qc, backend, shots=1)
        result = job.result()
        quantum_data = list(result.get_counts(qc).keys())[0]

        # Convert quantum data to input format
        quantum_input = torch.Tensor([float(bit) for bit in quantum_data])

        # Classical GAN training steps
        real_data = data[0]
        fake_data = G(quantum_input)

        # Update Discriminator
        D.zero_grad()
        real_output = D(real_data)
        fake_output = D(fake_data.detach())
        lossD = criterion(real_output, torch.ones_like(real_output)) + criterion(fake_output, torch.zeros_like(fake_output))
        lossD.backward()
        optimizerD.step()

        # Update Generator
        G.zero_grad()
        fake_output = D(fake_data)
        lossG = criterion(fake_output, torch.ones_like(fake_output))
        lossG.backward()
        optimizerG.step()

        # Print losses (optional)
        print(f'Epoch [{epoch}/{epochs}], Step [{i}/{len(dataloader)}], Loss D: {lossD.item()}, Loss G: {lossG.item()}')

Step 6: Prepare GIS Data for Training

Load and preprocess GIS data for use in the QGAN.

# Load GIS data using GeoPandas
gis_data = gpd.read_file('path_to_my_gis_data.shp')

# Preprocess GIS data
# Example: normalize data, convert to tensor, etc.
gis_data = gis_data.to_crs(epsg=4326)  # Convert to a common coordinate reference system
data_tensor = torch.tensor(gis_data.values, dtype=torch.float32)

Step 7: Train and Evaluate the QGAN

Run the training process and evaluate the model’s performance.

# Training and evaluation loop
for epoch in range(epochs):
    for i, data in enumerate(dataloader):
        # Quantum and classical data integration and training
        # my training code here...

# Save the model
torch.save(G.state_dict(), 'generator_model.pth')
torch.save(D.state_dict(), 'discriminator_model.pth')

# Evaluate the model
# my evaluation code here...

Step 8: Run on the HPC System

Submitjob to the HPC system using a job scheduler like SLURM or PBS. Here is an example SLURM script:

#!/bin/bash
#SBATCH --job-name=qgan_gis
#SBATCH --output=qgan_gis_output.txt
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=4
#SBATCH --mem=16G
#SBATCH --time=24:00:00
#SBATCH --partition=gpu
#SBATCH --gpus=1

module load gcc/11.2.0
module load conda
module load nvidia/23.5

conda activate qgan_env

python my_qgan_script.py

Save this script as submit_job.sh and submit it to the job scheduler:

sbatch submit_job.sh

HPC conda

name: essential_env channels: - defaults - conda-forge dependencies: - python=3.10 - numpy - pandas - matplotlib - scipy - scikit-learn - jupyter - pip - pip: - qiskit - pytorch - torchvision - tensorflow - keras - plotly - seaborn

conda env create -f environment3.yml

pip uninstall ultralytics rustworkx numpy pip install ultralytics rustworkx==0.14.2

conda env export > environment.yml

conda update –all –dry-run

conda create -n new_env_name python=3.10
conda activate new_env_name
conda install numpy scipy matplotlib seaborn ...
conda env export > environment_name.yml
dependencies:
  - numpy=1.26.4
  - scipy=1.14.0
  - matplotlib=3.8.4
  - seaborn=0.13.2
  - ...

``{sh} conda env create -f environment_name.yml



<!-- rnb-text-end -->


<!-- rnb-chunk-begin -->


<!-- rnb-source-begin eyJkYXRhIjoiYGBgYmFzaFxuY29uZGEgY29uZmlnIC0tYWRkIGNoYW5uZWxzIGNvbmRhLWZvcmdlXG5jb25kYSBjb25maWcgLS1zZXQgY2hhbm5lbF9wcmlvcml0eSBzdHJpY3RcbmBgYCJ9 -->

```bash
conda config --add channels conda-forge
conda config --set channel_priority strict

nano environment.yml - save as new

name: my_new_env
channels:
  - conda-forge
  - defaults
dependencies:
  - python=3.10
  - numpy=1.26.4
  - scipy=1.14.0
  - matplotlib=3.8.4
  - seaborn=0.13.2
  - pandas=2.2.2
  - tqdm=4.66.4
  - jupyter
  - ipykernel
  - scikit-learn=1.5.0
  - pytorch=2.3.1
  - torchvision=0.18.1
  - qiskit=1.1.1
  - pyunicorn
  - tensorflow
  - keras
  - plotly
  - h5py
  - h5netcdf
  - folium
  - cartopy
  - py-opencv
  - opencv

###conda env export > environment_name.yml

module load gcc/11.2.0

module load conda

salloc --nodes=1 --ntasks=1 --cpus-per-task=4 --mem=16G --time=02:00:00 --partition=batch --gres=gpu:1



module load gcc/11.2.0
module load conda
conda activate qgan_env

Full Command Sequence
Allocate resources on the HPC system:

sh
Copy code
salloc --nodes=1 --ntasks=1 --cpus-per-task=4 --mem=16G --time=02:00:00 --partition=batch --gres=gpu:1
Load necessary modules and activate Conda environment:

sh
Copy code
module load gcc/11.2.0
module load conda
conda activate qgan_env
conda install jupyter
Start Jupyter Notebook:

sh
Copy code
jupyter-notebook --no-browser --port=8888
If this fails, try JupyterLab:

sh
Copy code
jupyter lab --no-browser --port=8888
Set up SSH tunneling on your local machine:

sh
Copy code
ssh -N -L 8888:localhost:8888 jmcphaul@slogin-02
Access Jupyter Notebook via your web browser:

ruby
Copy code
http://localhost:8888/?token=YOUR_TOKEN
LS0tDQp0aXRsZTogInN1cGVycG9kIG1vZHVsZXMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpNb2R1bGVzIGluIHRoZSBjb250ZXh0IG9mIEhpZ2gtUGVyZm9ybWFuY2UgQ29tcHV0aW5nIChIUEMpIHN5c3RlbXMgYXJlDQpwYWNrYWdlcyB0aGF0IEkgY2FuIGxvYWQgYW5kIHVubG9hZCBkeW5hbWljYWxseSB0byBjb25maWd1cmUgbXkNCmVudmlyb25tZW50KHMpIHdpdGggdGhlIG5lY2Vzc2FyeSBzb2Z0d2FyZS4gSGVyZeKAmXMgYSBicmllZiBleHBsYW5hdGlvbg0Kb2YgZWFjaCBtb2R1bGUgbGlzdGVkOg0KDQojIyMgQ29tcGlsZXIgTW9kdWxlcw0KDQoxLiAgKiphbWQvYW9jYy80LjEuMCoqDQogICAgLSAgICoqQU1EIEFPQ0MgKEFNRCBPcHRpbWl6aW5nIEMvQysrIENvbXBpbGVyKSoqOiBUaGlzIGlzIGENCiAgICAgICAgaGlnaC1wZXJmb3JtYW5jZSwgcHJvZHVjdGlvbi1xdWFsaXR5IGNvZGUgZ2VuZXJhdGlvbiB0b29sLiBJdA0KICAgICAgICBzdXBwb3J0cyBDLCBDKyssIGFuZCBGb3J0cmFuIHByb2dyYW1taW5nIGxhbmd1YWdlcyBhbmQgaXMNCiAgICAgICAgc3BlY2lmaWNhbGx5IG9wdGltaXplZCBmb3IgQU1EIHByb2Nlc3NvcnMuDQoyLiAgKipnY2MvMTEuMi4wIGFuZCBnY2MvMTMuMi4wKioNCiAgICAtICAgKipHQ0MgKEdOVSBDb21waWxlciBDb2xsZWN0aW9uKSoqOiBUaGVzZSBhcmUgd2lkZWx5LXVzZWQNCiAgICAgICAgY29tcGlsZXJzIGZvciBDLCBDKyssIGFuZCBGb3J0cmFuLiBHQ0MgMTEuMi4wIGFuZCBHQ0MgMTMuMi4wIGFyZQ0KICAgICAgICBkaWZmZXJlbnQgdmVyc2lvbnMsIHdpdGggdGhlIGxhdHRlciBiZWluZyB0aGUgZGVmYXVsdCAoZGVub3RlZA0KICAgICAgICBieSAnRCcpIHZlcnNpb24gaW4gdGhpcyBlbnZpcm9ubWVudC4gR0NDIGlzIGtub3duIGZvciBpdHMNCiAgICAgICAgcG9ydGFiaWxpdHkgYW5kIGlzIGEgc3RhbmRhcmQgY29tcGlsZXIgb24gbWFueSBMaW51eA0KICAgICAgICBkaXN0cmlidXRpb25zLg0KMy4gICoqaW50ZWwvb25lYXBpLzIwMjMuMioqDQogICAgLSAgICoqSW50ZWwgT25lQVBJKio6IFRoaXMgaXMgYSBjb21wcmVoZW5zaXZlLCBjcm9zcy1hcmNoaXRlY3R1cmUNCiAgICAgICAgcHJvZ3JhbW1pbmcgdG9vbGtpdC4gSXQgaW5jbHVkZXMgY29tcGlsZXJzIChsaWtlIElDQyBmb3IgQy9DKysNCiAgICAgICAgYW5kIElGT1JUIGZvciBGb3J0cmFuKSwgbGlicmFyaWVzLCBhbmQgYW5hbHlzaXMgdG9vbHMgb3B0aW1pemVkDQogICAgICAgIGZvciBJbnRlbCBwcm9jZXNzb3JzLiBPbmVBUEkgaXMgZGVzaWduZWQgdG8gaGVscCBkZXZlbG9wZXJzDQogICAgICAgIGJ1aWxkIGhpZ2gtcGVyZm9ybWFuY2UgYXBwbGljYXRpb25zLg0KNC4gICoqbnZpZGlhL252aHBjLzIzLjcqKg0KICAgIC0gICAqKk5WSURJQSBIUEMgU0RLKio6IFRoaXMgaW5jbHVkZXMgY29tcGlsZXJzLCBsaWJyYXJpZXMsIGFuZA0KICAgICAgICB0b29scyB0byBkZXZlbG9wIGFwcGxpY2F0aW9ucyBmb3IgTlZJRElBIEdQVXMgYW5kIENQVXMuIFRoZQ0KICAgICAgICBOVkhQQyAoZm9ybWVybHkgUEdJKSBjb21waWxlciBzdWl0ZSBzdXBwb3J0cyBDLCBDKyssIGFuZCBGb3J0cmFuDQogICAgICAgIHByb2dyYW1taW5nIGxhbmd1YWdlcyBhbmQgaXMgb3B0aW1pemVkIGZvciBDVURBIGFuZA0KICAgICAgICBHUFUtYWNjZWxlcmF0ZWQgYXBwbGljYXRpb25zLg0KDQojIyMgQXBwbGljYXRpb24gTW9kdWxlcw0KDQoxLiAgKiphbWJlci8yMioqDQogICAgLSAgICoqQU1CRVIgKEFzc2lzdGVkIE1vZGVsIEJ1aWxkaW5nIHdpdGggRW5lcmd5IFJlZmluZW1lbnQpKio6IEENCiAgICAgICAgc3VpdGUgb2YgYmlvbW9sZWN1bGFyIHNpbXVsYXRpb24gcHJvZ3JhbXMuIEl0IGlzIHVzZWQgcHJpbWFyaWx5DQogICAgICAgIGZvciBtb2xlY3VsYXIgZHluYW1pY3Mgc2ltdWxhdGlvbnMgb2YgcHJvdGVpbnMgYW5kIG51Y2xlaWMNCiAgICAgICAgYWNpZHMuDQoyLiAgKiphcHB0YWluZXIvMS4xLjkqKg0KICAgIC0gICAqKkFwcHRhaW5lcioqOiBGb3JtZXJseSBrbm93biBhcyBTaW5ndWxhcml0eSwgaXQgaXMgYSBjb250YWluZXINCiAgICAgICAgcGxhdGZvcm0gZGVzaWduZWQgZm9yIEhQQyBlbnZpcm9ubWVudHMuIEl0IGFsbG93cyB1c2VycyB0bw0KICAgICAgICBjcmVhdGUgYW5kIHJ1biBjb250YWluZXJzIHRoYXQgcGFja2FnZSB1cCBhcHBsaWNhdGlvbnMgYW5kIHRoZWlyDQogICAgICAgIGRlcGVuZGVuY2llcyBpbiBhIHBvcnRhYmxlIG1hbm5lci4NCjMuICAqKmNvbmRhKioNCiAgICAtICAgKipDb25kYSoqOiBBbiBvcGVuLXNvdXJjZSBwYWNrYWdlIG1hbmFnZW1lbnQgYW5kIGVudmlyb25tZW50DQogICAgICAgIG1hbmFnZW1lbnQgc3lzdGVtLiBJdCBhbGxvd3MgeW91IHRvIGNyZWF0ZSBpc29sYXRlZCBlbnZpcm9ubWVudHMNCiAgICAgICAgYW5kIGluc3RhbGwgcGFja2FnZXMgZnJvbSB0aGUgQ29uZGEgcmVwb3NpdG9yeS4gSXQgaXMNCiAgICAgICAgcGFydGljdWxhcmx5IHBvcHVsYXIgZm9yIG1hbmFnaW5nIFB5dGhvbiBlbnZpcm9ubWVudHMgYW5kDQogICAgICAgIHBhY2thZ2VzLg0KNC4gICoqZ2F1c3NpYW4vZzE2YzAyKioNCiAgICAtICAgKipHYXVzc2lhbioqOiBTb2Z0d2FyZSB1c2VkIGZvciBjb21wdXRhdGlvbmFsIGNoZW1pc3RyeS4NCiAgICAgICAgR2F1c3NpYW4gMTYgaXMgYSB2ZXJzaW9uIG9mIHRoZSBHYXVzc2lhbiBzb2Z0d2FyZSBzdWl0ZSwgd2hpY2gNCiAgICAgICAgcGVyZm9ybXMgZWxlY3Ryb25pYyBzdHJ1Y3R1cmUgY2FsY3VsYXRpb25zLiBJdCBpcyB3aWRlbHkgdXNlZCBpbg0KICAgICAgICBjaGVtaXN0cnkgZm9yIHByZWRpY3RpbmcgdGhlIGVuZXJnaWVzLCBtb2xlY3VsYXIgc3RydWN0dXJlcywgYW5kDQogICAgICAgIHZpYnJhdGlvbmFsIGZyZXF1ZW5jaWVzIG9mIG1vbGVjdWxhciBzeXN0ZW1zLg0KNS4gICoqanVsaWEvMS45LjIqKg0KICAgIC0gICAqKkp1bGlhKio6IEEgaGlnaC1sZXZlbCwgaGlnaC1wZXJmb3JtYW5jZSBwcm9ncmFtbWluZyBsYW5ndWFnZQ0KICAgICAgICBmb3IgdGVjaG5pY2FsIGNvbXB1dGluZy4gSXQgaXMgZGVzaWduZWQgZm9yIG51bWVyaWNhbCBhbmQNCiAgICAgICAgc2NpZW50aWZpYyBjb21wdXRpbmcsIGFuZCB2ZXJzaW9uIDEuOS4yIGlzIGEgc3BlY2lmaWMgcmVsZWFzZSBvZg0KICAgICAgICB0aGlzIGxhbmd1YWdlLg0KNi4gICoqbGFtbXBzL21heTIyKioNCiAgICAtICAgKipMQU1NUFMgKExhcmdlLXNjYWxlIEF0b21pYy9Nb2xlY3VsYXIgTWFzc2l2ZWx5IFBhcmFsbGVsDQogICAgICAgIFNpbXVsYXRvcikqKjogQSBjbGFzc2ljYWwgbW9sZWN1bGFyIGR5bmFtaWNzIGNvZGUgdGhhdCBjYW4gbW9kZWwNCiAgICAgICAgYW4gZW5zZW1ibGUgb2YgcGFydGljbGVzIGluIGEgbGlxdWlkLCBzb2xpZCwgb3IgZ2FzZW91cyBzdGF0ZS4NCiAgICAgICAgTEFNTVBTIGlzIHdpZGVseSB1c2VkIGluIG1hdGVyaWFscyBzY2llbmNlLCBjaGVtaXN0cnksIGFuZA0KICAgICAgICBwaHlzaWNzLg0KNy4gICoqc3BhY2sqKg0KICAgIC0gICAqKlNwYWNrKio6IEEgZmxleGlibGUgcGFja2FnZSBtYW5hZ2VyIHRoYXQgc3VwcG9ydHMgbXVsdGlwbGUNCiAgICAgICAgdmVyc2lvbnMgYW5kIGNvbmZpZ3VyYXRpb25zIG9mIHNvZnR3YXJlLiBJdCBpcyBwYXJ0aWN1bGFybHkNCiAgICAgICAgZGVzaWduZWQgZm9yIEhQQyBlbnZpcm9ubWVudHMgYW5kIGFsbG93cyB1c2VycyB0byBlYXNpbHkNCiAgICAgICAgaW5zdGFsbCwgbWFuYWdlLCBhbmQgc3dhcCBzb2Z0d2FyZSBwYWNrYWdlcy4NCg0KVG8gdXNlIHRoZSBOVklESUEgTlZIUEMgMjMuNyBjb21waWxlciBzdWl0ZSBvbiBIUEMgc3lzdGVtOg0KDQojIyMgU3RlcCAxOiBMb2FkIHRoZSBOVkhQQyBNb2R1bGUNCg0KTG9hZCB0aGUgTlZJRElBIE5WSFBDIG1vZHVsZSAtc2V0cyB1cCBlbnZpcm9ubWVudCB3aXRoIHRoZSBuZWNlc3NhcnkNCnBhdGhzIGFuZCBlbnZpcm9ubWVudCB2YXJpYWJsZXMuDQoNCmBgYCBzaA0KbW9kdWxlIGxvYWQgbnZpZGlhL252aHBjLzIzLjcNCmBgYA0KDQpWZXJpZnkgdGhhdCB0aGUgbW9kdWxlIGhhcyBiZWVuIGxvYWRlZCBjb3JyZWN0bHkgYnkgY2hlY2tpbmcgdGhlDQpjdXJyZW50bHkgbG9hZGVkIG1vZHVsZXM6DQoNCmBgYCBzaA0KbW9kdWxlIGxpc3QNCmBgYA0KDQojIyMgU3RlcCAyOiBDb21waWxlIG15IENvZGUNCg0KT25jZSBsb2FkZWQsdXNlIHRoZSBOVkhQQyBjb21waWxlcnMgdG8gY29tcGlsZSBjb2RlLiBOVkhQQyBpbmNsdWRlcw0Kc2V2ZXJhbCBjb21waWxlcnM6DQoNCi0gICBgbnZjYCBmb3IgQw0KLSAgIGBudmMrK2AgZm9yIEMrKw0KLSAgIGBudmZvcnRyYW5gIGZvciBGb3J0cmFuDQoNCkhlcmUgYXJlIGV4YW1wbGVzIG9mIGhvdyB0byBjb21waWxlIEMsIEMrKywgYW5kIEZvcnRyYW4gY29kZToNCg0KIyMjIyBDIENvZGUgQ29tcGlsYXRpb24NCg0KYGBgIHNoDQpudmMgLW8gbXlfcHJvZ3JhbSBteV9wcm9ncmFtLmMNCmBgYA0KDQojIyMjIEMrKyBDb2RlIENvbXBpbGF0aW9uDQoNCmBgYCBzaA0KbnZjKysgLW8gbXlfcHJvZ3JhbSBteV9wcm9ncmFtLmNwcA0KYGBgDQoNCiMjIyMgRm9ydHJhbiBDb2RlIENvbXBpbGF0aW9uDQoNCmBgYCBzaA0KbnZmb3J0cmFuIC1vIG15X3Byb2dyYW0gbXlfcHJvZ3JhbS5mOTANCmBgYA0KDQojIyMgU3RlcCAzOiBSdW4gbXkgUHJvZ3JhbQ0KDQpBZnRlciBjb21waWxpbmcgY29kZSBydW4gdGhlIGV4ZWN1dGFibGUgYXMgSSB3b3VsZCBhbnkgb3RoZXIgcHJvZ3JhbToNCg0KYGBgIHNoDQouL215X3Byb2dyYW0NCmBgYA0KDQojIyMgU3RlcCA0OiBVc2luZyBDVURBDQoNCklmIGNvZGUgdXRpbGl6ZXMgQ1VEQSBmb3IgR1BVIGFjY2VsZXJhdGlvbiwgTlZIUEMgc3VwcG9ydHMgQ1VEQQ0KZGlyZWN0bHkuIEhlcmUgaXMgYW4gZXhhbXBsZSBvZiBjb21waWxpbmcgYSBDVURBIHByb2dyYW06DQoNCmBgYCBzaA0KbnZjKysgLW8gbXlfY3VkYV9wcm9ncmFtIG15X2N1ZGFfcHJvZ3JhbS5jdQ0KYGBgDQoNCiMjIyBFeGFtcGxlIFdvcmtmbG93DQoNCkV4YW1wbGUgd29ya2Zsb3c6DQoNCjEuICAqKkxvYWQgdGhlIE5WSFBDIG1vZHVsZToqKg0KDQogICAgYGBgIHNoDQogICAgbW9kdWxlIGxvYWQgbnZpZGlhL252aHBjLzIzLjcNCiAgICBgYGANCg0KMi4gICoqQ29tcGlsZSBDIHByb2dyYW06KioNCg0KICAgIGBgYCBzaA0KICAgIG52YyAtbyBoZWxsb193b3JsZCBoZWxsb193b3JsZC5jDQogICAgYGBgDQoNCjMuICAqKlJ1biBjb21waWxlZCBwcm9ncmFtOioqDQoNCiAgICBgYGAgc2gNCiAgICAuL2hlbGxvX3dvcmxkDQogICAgYGBgDQoNCiMjIyBBZGRpdGlvbmFsIFRvb2xzIGFuZCBMaWJyYXJpZXMNCg0KTlZIUEMgYWxzbyBjb21lcyB3aXRoIGFkZGl0aW9uYWwgdG9vbHMgYW5kIGxpYnJhcmllcywgc3VjaCBhczoNCg0KLSAgICoqTWF0aCBMaWJyYXJpZXM6KiogT3B0aW1pemVkIGxpYnJhcmllcyBsaWtlIGN1QkxBUywgY3VGRlQsIGV0Yy4NCi0gICAqKkRlYnVnZ2VyIGFuZCBQcm9maWxlcjoqKiBUb29scyBsaWtlIGBjdWRhLWdkYmAgYW5kDQogICAgYG5zaWdodC1jb21wdXRlYCBmb3IgZGVidWdnaW5nIGFuZCBwcm9maWxpbmcgR1BVIGFwcGxpY2F0aW9ucy4NCg0KVG8gYWNjZXNzIHRoZSBkb2N1bWVudGF0aW9uIGFuZCBhZGRpdGlvbmFsIGhlbHAgZm9yIE5WSFBDIHRvb2xzLCBjYW4gdXNlDQp0aGUgYG1hbmAgY29tbWFuZCBvciByZWZlciB0byB0aGUgb25saW5lIGRvY3VtZW50YXRpb24gcHJvdmlkZWQgYnkNCk5WSURJQS4NCg0KIyMjIEV4YW1wbGUgQ1VEQSBQcm9ncmFtIENvbXBpbGF0aW9uDQoNCkhlcmXigJlzIGEgYnJpZWYgZXhhbXBsZSBvZiBjb21waWxpbmcgYSBDVURBIHByb2dyYW06DQoNCioqaGVsbG9fd29ybGQuY3U6KioNCg0KYGBgIGNwcA0KI2luY2x1ZGUgPHN0ZGlvLmg+DQoNCl9fZ2xvYmFsX18gdm9pZCBoZWxsb0Zyb21HUFUoKSB7DQogICAgcHJpbnRmKCJIZWxsbyBXb3JsZCBmcm9tIEdQVSFcbiIpOw0KfQ0KDQppbnQgbWFpbigpIHsNCiAgICBoZWxsb0Zyb21HUFU8PDwxLCAxPj4+KCk7DQogICAgY3VkYURldmljZVN5bmNocm9uaXplKCk7DQogICAgcmV0dXJuIDA7DQp9DQpgYGANCg0KKipDb21waWxpbmcgYW5kIFJ1bm5pbmc6KioNCg0KMS4gICoqQ29tcGlsZToqKg0KDQogICAgYGBgIHNoDQogICAgbnZjKysgLW8gaGVsbG9fd29ybGQgaGVsbG9fd29ybGQuY3UNCiAgICBgYGANCg0KMi4gICoqUnVuOioqDQoNCiAgICBgYGAgc2gNCiAgICAuL2hlbGxvX3dvcmxkDQogICAgYGBgDQoNCkNyZWF0aW5nIGEgUXVhbnR1bSBHZW5lcmF0aXZlIEFkdmVyc2FyaWFsIE5ldHdvcmsgKFFHQU4pIGZvciBhbm9tYWx5DQpkZXRlY3Rpb24gaW4gR2VvZ3JhcGhpYyBJbmZvcm1hdGlvbiBTeXN0ZW1zIChHSVMpIGlzIGFuIGFkdmFuY2VkIHRhc2sNCnRoYXQgaW52b2x2ZXMgaW50ZWdyYXRpbmcgY2xhc3NpY2FsIGFuZCBxdWFudHVtIGNvbXB1dGluZyByZXNvdXJjZXMuDQpIZXJl4oCZcyBhIGRldGFpbGVkIGd1aWRlIG9uIGhvdyB0byBzZXQgdXAgdGhlIGVudmlyb25tZW50IGFuZCB0aGUgdG9vbHMgSQ0KY291bGQgdXNlOg0KDQojIyMgU3RlcCAxOiBDaG9vc2UgdGhlIFJpZ2h0IEVudmlyb25tZW50IGFuZCBUb29scw0KDQoxLiAgKipRdWFudHVtIENvbXB1dGluZyBTREsqKjogVXNlIElCTeKAmXMgUWlza2l0IG9yIEdvb2dsZeKAmXMgQ2lycSBmb3INCiAgICBxdWFudHVtIGNvbXB1dGluZyBjb21wb25lbnRzLg0KMi4gICoqQ2xhc3NpY2FsIENvbXB1dGluZyBFbnZpcm9ubWVudCoqOiBVc2UgUHl0aG9uIHdpdGggbGlicmFyaWVzIHN1Y2gNCiAgICBhcyBUZW5zb3JGbG93IG9yIFB5VG9yY2ggZm9yIGNsYXNzaWNhbCBtYWNoaW5lIGxlYXJuaW5nIGNvbXBvbmVudHMuDQozLiAgKipHSVMgVG9vbHMqKjogVXNlIGxpYnJhcmllcyBsaWtlIEdlb1BhbmRhcywgR0RBTCwgb3Igb3RoZXIgcmVsZXZhbnQNCiAgICBHSVMgdG9vbHMgaW4gUHl0aG9uLg0KNC4gICoqQ29tcGlsZXIqKjogWW91IGNhbiB1c2UgdGhlIE5WSURJQSBOVkhQQyBjb21waWxlciBmb3Igb3B0aW1pemluZw0KICAgIGNsYXNzaWNhbCBjb2RlLCBidXQgUHl0aG9uIGVudmlyb25tZW50cyB1c3VhbGx5IHVzZSBzdGFuZGFyZA0KICAgIGludGVycHJldGVycy4NCg0KIyMjIFN0ZXAgMjogU2V0IFVwIHRoZSBDbGFzc2ljYWwgRW52aXJvbm1lbnQNCg0KRmlyc3QsIGVuc3VyZSB0aGF0IEkgaGF2ZSBQeXRob24gYW5kIG5lY2Vzc2FyeSBwYWNrYWdlcyBpbnN0YWxsZWQuIFVzaW5nDQpgY29uZGFgIHRvIGNyZWF0ZSBhbiBpc29sYXRlZCBlbnZpcm9ubWVudCBpcyByZWNvbW1lbmRlZC4NCg0KIyMjIyAxLiBJbnN0YWxsIEFuYWNvbmRhIG9yIE1pbmljb25kYQ0KDQpEb3dubG9hZCBhbmQgaW5zdGFsbCBBbmFjb25kYSBvciBNaW5pY29uZGEgZnJvbSB0aGUgb2ZmaWNpYWwgd2Vic2l0ZS4NCg0KIyMjIyAyLiBDcmVhdGUgYSBDb25kYSBFbnZpcm9ubWVudA0KDQpgYGAgc2gNCmNvbmRhIGNyZWF0ZSAtbiBxZ2FuX2VudiBweXRob249My45DQpjb25kYSBhY3RpdmF0ZSBxZ2FuX2Vudg0KYGBgDQoNCiMjIyMgMy4gSW5zdGFsbCBSZXF1aXJlZCBQYWNrYWdlcw0KDQpgYGAgc2gNCmNvbmRhIGluc3RhbGwgbnVtcHkgcGFuZGFzIGdlb3BhbmRhcyBnZGFsIG1hdHBsb3RsaWINCnBpcCBpbnN0YWxsIHFpc2tpdCB0ZW5zb3JmbG93IHRvcmNoIHRvcmNodmlzaW9uDQpgYGANCg0KIyMjIFN0ZXAgMzogU2V0IFVwIFF1YW50dW0gQ29tcHV0aW5nIFNESw0KDQojIyMjIDEuIEluc3RhbGwgUWlza2l0DQoNCklmIHVzaW5nIElCTeKAmXMgUWlza2l0Og0KDQpgYGAgc2gNCnBpcCBpbnN0YWxsIHFpc2tpdA0KYGBgDQoNCkZvciBDaXJxIChHb29nbGUpOg0KDQpgYGAgc2gNCnBpcCBpbnN0YWxsIGNpcnENCmBgYA0KDQojIyMgU3RlcCA0OiBJbnRlZ3JhdGUgQ2xhc3NpY2FsIGFuZCBRdWFudHVtIENvbXBvbmVudHMNCg0KSGVyZeKAmXMgYW4gZXhhbXBsZSBvZiBhIHdvcmtmbG93IGludGVncmF0aW5nIGNsYXNzaWNhbCBhbmQgcXVhbnR1bQ0KY29tcG9uZW50cyBmb3IgYSBRR0FOOg0KDQojIyMjIDEuIENsYXNzaWNhbCBHQU4gZm9yIEdJUyBEYXRhDQoNCkNyZWF0ZSBhIGNsYXNzaWNhbCBHQU4gZm9yIGdlbmVyYXRpbmcgR0lTIGRhdGEgYW5vbWFsaWVzLiBCZWxvdyBpcyBhDQpzaW1wbGlmaWVkIGV4YW1wbGUgdXNpbmcgUHlUb3JjaDoNCg0KYGBgIHB5dGhvbg0KaW1wb3J0IHRvcmNoDQppbXBvcnQgdG9yY2gubm4gYXMgbm4NCmltcG9ydCB0b3JjaC5vcHRpbSBhcyBvcHRpbQ0KaW1wb3J0IGdlb3BhbmRhcyBhcyBncGQNCg0KIyBFeGFtcGxlOiBTaW1wbGUgR0FOIGNvbXBvbmVudHMNCmNsYXNzIEdlbmVyYXRvcihubi5Nb2R1bGUpOg0KICAgIGRlZiBfX2luaXRfXyhzZWxmKToNCiAgICAgICAgc3VwZXIoR2VuZXJhdG9yLCBzZWxmKS5fX2luaXRfXygpDQogICAgICAgIHNlbGYubWFpbiA9IG5uLlNlcXVlbnRpYWwoDQogICAgICAgICAgICBubi5MaW5lYXIoMTAwLCAyNTYpLA0KICAgICAgICAgICAgbm4uUmVMVSgpLA0KICAgICAgICAgICAgbm4uTGluZWFyKDI1NiwgNTEyKSwNCiAgICAgICAgICAgIG5uLlJlTFUoKSwNCiAgICAgICAgICAgIG5uLkxpbmVhcig1MTIsIDEwMjQpLA0KICAgICAgICAgICAgbm4uVGFuaCgpDQogICAgICAgICkNCiAgICANCiAgICBkZWYgZm9yd2FyZChzZWxmLCBpbnB1dCk6DQogICAgICAgIHJldHVybiBzZWxmLm1haW4oaW5wdXQpDQoNCmNsYXNzIERpc2NyaW1pbmF0b3Iobm4uTW9kdWxlKToNCiAgICBkZWYgX19pbml0X18oc2VsZik6DQogICAgICAgIHN1cGVyKERpc2NyaW1pbmF0b3IsIHNlbGYpLl9faW5pdF9fKCkNCiAgICAgICAgc2VsZi5tYWluID0gbm4uU2VxdWVudGlhbCgNCiAgICAgICAgICAgIG5uLkxpbmVhcigxMDI0LCA1MTIpLA0KICAgICAgICAgICAgbm4uTGVha3lSZUxVKDAuMiksDQogICAgICAgICAgICBubi5MaW5lYXIoNTEyLCAyNTYpLA0KICAgICAgICAgICAgbm4uTGVha3lSZUxVKDAuMiksDQogICAgICAgICAgICBubi5MaW5lYXIoMjU2LCAxKSwNCiAgICAgICAgICAgIG5uLlNpZ21vaWQoKQ0KICAgICAgICApDQogICAgDQogICAgZGVmIGZvcndhcmQoc2VsZiwgaW5wdXQpOg0KICAgICAgICByZXR1cm4gc2VsZi5tYWluKGlucHV0KQ0KDQojIEluaXRpYWxpemUgbW9kZWxzDQpHID0gR2VuZXJhdG9yKCkNCkQgPSBEaXNjcmltaW5hdG9yKCkNCg0KIyBPcHRpbWl6ZXJzIGFuZCBsb3NzIGZ1bmN0aW9uDQpvcHRpbWl6ZXJHID0gb3B0aW0uQWRhbShHLnBhcmFtZXRlcnMoKSwgbHI9MC4wMDAyKQ0Kb3B0aW1pemVyRCA9IG9wdGltLkFkYW0oRC5wYXJhbWV0ZXJzKCksIGxyPTAuMDAwMikNCmNyaXRlcmlvbiA9IG5uLkJDRUxvc3MoKQ0KDQojIFRyYWluaW5nIGxvb3AgcGxhY2Vob2xkZXINCmZvciBlcG9jaCBpbiByYW5nZShlcG9jaHMpOg0KICAgICMgVHJhaW5pbmcgY29kZSBoZXJlDQogICAgcGFzcw0KYGBgDQoNCiMjIyMgMi4gSW50ZWdyYXRlIFF1YW50dW0gQ29tcG9uZW50DQoNClVzaW5nIFFpc2tpdCBmb3IgcXVhbnR1bSBwYXJ0cyBvZiBRR0FOOg0KDQpgYGAgcHl0aG9uDQpmcm9tIHFpc2tpdCBpbXBvcnQgUXVhbnR1bUNpcmN1aXQsIHRyYW5zcGlsZSwgQWVyLCBleGVjdXRlDQpmcm9tIHFpc2tpdC52aXN1YWxpemF0aW9uIGltcG9ydCBwbG90X2hpc3RvZ3JhbQ0KDQojIFF1YW50dW0gZ2VuZXJhdG9yDQpkZWYgcXVhbnR1bV9nZW5lcmF0b3IoKToNCiAgICBxYyA9IFF1YW50dW1DaXJjdWl0KDEsIDEpDQogICAgcWMuaCgwKQ0KICAgIHFjLm1lYXN1cmUoMCwgMCkNCiAgICByZXR1cm4gcWMNCg0KIyBTaW11bGF0ZSB0aGUgcXVhbnR1bSBjaXJjdWl0DQpiYWNrZW5kID0gQWVyLmdldF9iYWNrZW5kKCdxYXNtX3NpbXVsYXRvcicpDQpxYyA9IHF1YW50dW1fZ2VuZXJhdG9yKCkNCmpvYiA9IGV4ZWN1dGUocWMsIGJhY2tlbmQsIHNob3RzPTEwMDApDQpyZXN1bHQgPSBqb2IucmVzdWx0KCkNCmNvdW50cyA9IHJlc3VsdC5nZXRfY291bnRzKHFjKQ0KcHJpbnQoY291bnRzKQ0KDQpwbG90X2hpc3RvZ3JhbShjb3VudHMpDQpgYGANCg0KIyMjIFN0ZXAgNTogSW50ZWdyYXRlIENsYXNzaWNhbCBhbmQgUXVhbnR1bSBDb21wb25lbnRzIGluIHRoZSBRR0FODQoNCkNvbWJpbmUgY2xhc3NpY2FsIGFuZCBxdWFudHVtIGNvbXBvbmVudHMuIEhlcmXigJlzIGEgc2ltcGxpZmllZCBwc2V1ZG9jb2RlDQppbnRlZ3JhdGlvbjoNCg0KYGBgIHB5dGhvbg0KIyBFeGFtcGxlIG9mIGludGVncmF0aW5nIHF1YW50dW0tZ2VuZXJhdGVkIGRhdGEgd2l0aCBjbGFzc2ljYWwgR0FOIHRyYWluaW5nDQpmb3IgZXBvY2ggaW4gcmFuZ2UoZXBvY2hzKToNCiAgICBmb3IgaSwgZGF0YSBpbiBlbnVtZXJhdGUoZGF0YWxvYWRlcik6DQogICAgICAgICMgUXVhbnR1bSBkYXRhIGdlbmVyYXRpb24NCiAgICAgICAgcWMgPSBxdWFudHVtX2dlbmVyYXRvcigpDQogICAgICAgIGpvYiA9IGV4ZWN1dGUocWMsIGJhY2tlbmQsIHNob3RzPTEpDQogICAgICAgIHJlc3VsdCA9IGpvYi5yZXN1bHQoKQ0KICAgICAgICBxdWFudHVtX2RhdGEgPSBsaXN0KHJlc3VsdC5nZXRfY291bnRzKHFjKS5rZXlzKCkpWzBdDQoNCiAgICAgICAgIyBDb252ZXJ0IHF1YW50dW0gZGF0YSB0byBpbnB1dCBmb3JtYXQNCiAgICAgICAgcXVhbnR1bV9pbnB1dCA9IHRvcmNoLlRlbnNvcihbZmxvYXQoYml0KSBmb3IgYml0IGluIHF1YW50dW1fZGF0YV0pDQoNCiAgICAgICAgIyBDbGFzc2ljYWwgR0FOIHRyYWluaW5nIHN0ZXBzDQogICAgICAgIHJlYWxfZGF0YSA9IGRhdGFbMF0NCiAgICAgICAgZmFrZV9kYXRhID0gRyhxdWFudHVtX2lucHV0KQ0KDQogICAgICAgICMgVXBkYXRlIERpc2NyaW1pbmF0b3INCiAgICAgICAgRC56ZXJvX2dyYWQoKQ0KICAgICAgICByZWFsX291dHB1dCA9IEQocmVhbF9kYXRhKQ0KICAgICAgICBmYWtlX291dHB1dCA9IEQoZmFrZV9kYXRhLmRldGFjaCgpKQ0KICAgICAgICBsb3NzRCA9IGNyaXRlcmlvbihyZWFsX291dHB1dCwgdG9yY2gub25lc19saWtlKHJlYWxfb3V0cHV0KSkgKyBjcml0ZXJpb24oZmFrZV9vdXRwdXQsIHRvcmNoLnplcm9zX2xpa2UoZmFrZV9vdXRwdXQpKQ0KICAgICAgICBsb3NzRC5iYWNrd2FyZCgpDQogICAgICAgIG9wdGltaXplckQuc3RlcCgpDQoNCiAgICAgICAgIyBVcGRhdGUgR2VuZXJhdG9yDQogICAgICAgIEcuemVyb19ncmFkKCkNCiAgICAgICAgZmFrZV9vdXRwdXQgPSBEKGZha2VfZGF0YSkNCiAgICAgICAgbG9zc0cgPSBjcml0ZXJpb24oZmFrZV9vdXRwdXQsIHRvcmNoLm9uZXNfbGlrZShmYWtlX291dHB1dCkpDQogICAgICAgIGxvc3NHLmJhY2t3YXJkKCkNCiAgICAgICAgb3B0aW1pemVyRy5zdGVwKCkNCg0KICAgICAgICAjIFByaW50IGxvc3NlcyAob3B0aW9uYWwpDQogICAgICAgIHByaW50KGYnRXBvY2ggW3tlcG9jaH0ve2Vwb2Noc31dLCBTdGVwIFt7aX0ve2xlbihkYXRhbG9hZGVyKX1dLCBMb3NzIEQ6IHtsb3NzRC5pdGVtKCl9LCBMb3NzIEc6IHtsb3NzRy5pdGVtKCl9JykNCmBgYA0KDQojIyMgQ29uY2x1c2lvbg0KDQoxLiAgKipTZXQgdXAgdGhlIGVudmlyb25tZW50Kio6IEluc3RhbGwgbmVjZXNzYXJ5IHRvb2xzIHVzaW5nIGBjb25kYWANCiAgICBhbmQgYHBpcGAuDQoyLiAgKipDcmVhdGUgY2xhc3NpY2FsIGFuZCBxdWFudHVtIG1vZGVscyoqOiBVc2UgUHlUb3JjaCBmb3IgY2xhc3NpY2FsDQogICAgR0FOIGFuZCBRaXNraXQgZm9yIHF1YW50dW0gY29tcG9uZW50cy4NCjMuICAqKkludGVncmF0ZSBtb2RlbHMqKjogQ29tYmluZSBxdWFudHVtLWdlbmVyYXRlZCBkYXRhIHdpdGggY2xhc3NpY2FsDQogICAgR0FOIHRyYWluaW5nLg0KDQojIENyZWF0ZSBhIFFHQU4gKFF1YW50dW0gR2VuZXJhdGl2ZSBBZHZlcnNhcmlhbCBOZXR3b3JrKSBmb3IgYW5vbWFseSBkZXRlY3Rpb24gaW4gR0lTIChHZW9ncmFwaGljIEluZm9ybWF0aW9uIFN5c3RlbXMpIG9uIHRoZSBIUEMgc3lzdGVtIEkgbmVlZCB0byBsZXZlcmFnZSBib3RoIHF1YW50dW0gYW5kIGNsYXNzaWNhbCBjb21wdXRpbmcgcmVzb3VyY2VzIGVmZmVjdGl2ZWx5LiBIZXJlIGlzIGEgc3RlcC1ieS1zdGVwIGd1aWRlIHRvIHNldCB1cCBteSBlbnZpcm9ubWVudCBhbmQgY29tcGlsZS9ydW4gbXkgUUdBTiBvbiB0aGUgSFBDIHN5c3RlbToNCg0KIyMjIFN0ZXAgMTogTG9hZCBOZWNlc3NhcnkgTW9kdWxlcw0KDQpGaXJzdCwgbG9hZCB0aGUgbW9kdWxlcyBuZWNlc3NhcnkgZm9yIGVudmlyb25tZW50LiBUaGlzIGluY2x1ZGVzIFB5dGhvbiwNCmFueSByZXF1aXJlZCBxdWFudHVtIGNvbXB1dGluZyBTREtzLCBhbmQgb3RoZXIgbGlicmFyaWVzLg0KDQpgYGAgc2gNCm1vZHVsZSBsb2FkIGdjYy8xMy4yLjANCm1vZHVsZSBsb2FkIG52aWRpYS9udmhwYy8yMy43DQptb2R1bGUgbG9hZCBjb25kYQ0KYGBgDQoNCiMjIyBTdGVwIDI6IENyZWF0ZSBhbmQgQWN0aXZhdGUgYSBDb25kYSBFbnZpcm9ubWVudA0KDQpVc2UgQ29uZGEgdG8gbWFuYWdlIG15IFB5dGhvbiBlbnZpcm9ubWVudCwgd2hpY2ggd2lsbCBpbmNsdWRlIHRoZQ0KbmVjZXNzYXJ5IGxpYnJhcmllcyBmb3IgYm90aCBxdWFudHVtIGFuZCBjbGFzc2ljYWwgY29tcHV0aW5nLg0KDQpgYGAgc2gNCmNvbmRhIGNyZWF0ZSAtbiBxZ2FuX2VudiBweXRob249My45DQpjb25kYSBhY3RpdmF0ZSBxZ2FuX2Vudg0KYGBgDQoNCiMjIyBTdGVwIDM6IEluc3RhbGwgUmVxdWlyZWQgUHl0aG9uIFBhY2thZ2VzDQoNCldpdGggQ29uZGEgZW52aXJvbm1lbnQgYWN0aXZhdGVkLCBpbnN0YWxsIHRoZSBuZWNlc3NhcnkgUHl0aG9uIHBhY2thZ2VzLg0KDQpgYGAgc2gNCiMgSW5zdGFsbCBiYXNpYyBwYWNrYWdlcyBmb3IgR0lTIGFuZCBxdWFudHVtIGNvbXB1dGluZw0KY29uZGEgaW5zdGFsbCBudW1weSBwYW5kYXMgZ2VvcGFuZGFzIGdkYWwgbWF0cGxvdGxpYg0KcGlwIGluc3RhbGwgcWlza2l0IHRlbnNvcmZsb3cgdG9yY2ggdG9yY2h2aXNpb24NCmBgYA0KDQojIyMgU3RlcCA0OiBTZXQgVXAgdGhlIFF1YW50dW0gQ29tcHV0aW5nIEVudmlyb25tZW50DQoNCiMjIyMgSW5zdGFsbCBRaXNraXQgKElCTSdzIFF1YW50dW0gQ29tcHV0aW5nIFNESykNCg0KYGBgIHNoDQpwaXAgaW5zdGFsbCBxaXNraXQNCmBgYA0KDQojIyMgU3RlcCA1OiBEZXZlbG9wIHRoZSBRR0FODQoNCkNyZWF0ZSB0aGUgUUdBTiBieSBpbnRlZ3JhdGluZyBib3RoIGNsYXNzaWNhbCBhbmQgcXVhbnR1bSBjb21wb25lbnRzLg0KQmVsb3cgaXMgYW4gZXhhbXBsZSBzZXR1cC4NCg0KIyMjIyBDbGFzc2ljYWwgR0FOIGZvciBHSVMgRGF0YSAodXNpbmcgUHlUb3JjaCkNCg0KYGBgIHB5dGhvbg0KaW1wb3J0IHRvcmNoDQppbXBvcnQgdG9yY2gubm4gYXMgbm4NCmltcG9ydCB0b3JjaC5vcHRpbSBhcyBvcHRpbQ0KaW1wb3J0IGdlb3BhbmRhcyBhcyBncGQNCmltcG9ydCBudW1weSBhcyBucA0KDQpjbGFzcyBHZW5lcmF0b3Iobm4uTW9kdWxlKToNCiAgICBkZWYgX19pbml0X18oc2VsZik6DQogICAgICAgIHN1cGVyKEdlbmVyYXRvciwgc2VsZikuX19pbml0X18oKQ0KICAgICAgICBzZWxmLm1haW4gPSBubi5TZXF1ZW50aWFsKA0KICAgICAgICAgICAgbm4uTGluZWFyKDEwMCwgMjU2KSwNCiAgICAgICAgICAgIG5uLlJlTFUoKSwNCiAgICAgICAgICAgIG5uLkxpbmVhcigyNTYsIDUxMiksDQogICAgICAgICAgICBubi5SZUxVKCksDQogICAgICAgICAgICBubi5MaW5lYXIoNTEyLCAxMDI0KSwNCiAgICAgICAgICAgIG5uLlRhbmgoKQ0KICAgICAgICApDQogICAgDQogICAgZGVmIGZvcndhcmQoc2VsZiwgaW5wdXQpOg0KICAgICAgICByZXR1cm4gc2VsZi5tYWluKGlucHV0KQ0KDQpjbGFzcyBEaXNjcmltaW5hdG9yKG5uLk1vZHVsZSk6DQogICAgZGVmIF9faW5pdF9fKHNlbGYpOg0KICAgICAgICBzdXBlcihEaXNjcmltaW5hdG9yLCBzZWxmKS5fX2luaXRfXygpDQogICAgICAgIHNlbGYubWFpbiA9IG5uLlNlcXVlbnRpYWwoDQogICAgICAgICAgICBubi5MaW5lYXIoMTAyNCwgNTEyKSwNCiAgICAgICAgICAgIG5uLkxlYWt5UmVMVSgwLjIpLA0KICAgICAgICAgICAgbm4uTGluZWFyKDUxMiwgMjU2KSwNCiAgICAgICAgICAgIG5uLkxlYWt5UmVMVSgwLjIpLA0KICAgICAgICAgICAgbm4uTGluZWFyKDI1NiwgMSksDQogICAgICAgICAgICBubi5TaWdtb2lkKCkNCiAgICAgICAgKQ0KICAgIA0KICAgIGRlZiBmb3J3YXJkKHNlbGYsIGlucHV0KToNCiAgICAgICAgcmV0dXJuIHNlbGYubWFpbihpbnB1dCkNCg0KIyBJbml0aWFsaXplIG1vZGVscw0KRyA9IEdlbmVyYXRvcigpDQpEID0gRGlzY3JpbWluYXRvcigpDQoNCiMgT3B0aW1pemVycyBhbmQgbG9zcyBmdW5jdGlvbg0Kb3B0aW1pemVyRyA9IG9wdGltLkFkYW0oRy5wYXJhbWV0ZXJzKCksIGxyPTAuMDAwMikNCm9wdGltaXplckQgPSBvcHRpbS5BZGFtKEQucGFyYW1ldGVycygpLCBscj0wLjAwMDIpDQpjcml0ZXJpb24gPSBubi5CQ0VMb3NzKCkNCg0KIyBEdW1teSB0cmFpbmluZyBsb29wIHBsYWNlaG9sZGVyDQplcG9jaHMgPSAxMDANCmZvciBlcG9jaCBpbiByYW5nZShlcG9jaHMpOg0KICAgICMgVHJhaW5pbmcgY29kZSBoZXJlDQogICAgcGFzcw0KYGBgDQoNCiMjIyMgSW50ZWdyYXRlIFF1YW50dW0gQ29tcG9uZW50IHVzaW5nIFFpc2tpdA0KDQpgYGAgcHl0aG9uDQpmcm9tIHFpc2tpdCBpbXBvcnQgUXVhbnR1bUNpcmN1aXQsIEFlciwgZXhlY3V0ZQ0KZnJvbSBxaXNraXQudmlzdWFsaXphdGlvbiBpbXBvcnQgcGxvdF9oaXN0b2dyYW0NCg0KZGVmIHF1YW50dW1fZ2VuZXJhdG9yKCk6DQogICAgcWMgPSBRdWFudHVtQ2lyY3VpdCgxLCAxKQ0KICAgIHFjLmgoMCkNCiAgICBxYy5tZWFzdXJlKDAsIDApDQogICAgcmV0dXJuIHFjDQoNCmJhY2tlbmQgPSBBZXIuZ2V0X2JhY2tlbmQoJ3Fhc21fc2ltdWxhdG9yJykNCnFjID0gcXVhbnR1bV9nZW5lcmF0b3IoKQ0Kam9iID0gZXhlY3V0ZShxYywgYmFja2VuZCwgc2hvdHM9MTAwMCkNCnJlc3VsdCA9IGpvYi5yZXN1bHQoKQ0KY291bnRzID0gcmVzdWx0LmdldF9jb3VudHMocWMpDQpwcmludChjb3VudHMpDQoNCnBsb3RfaGlzdG9ncmFtKGNvdW50cykNCmBgYA0KDQojIyMgU3RlcCA2OiBJbnRlZ3JhdGUgQ2xhc3NpY2FsIGFuZCBRdWFudHVtIENvbXBvbmVudHMgaW4gdGhlIFFHQU4NCg0KQ29tYmluZSBxdWFudHVtLWdlbmVyYXRlZCBkYXRhIHdpdGggY2xhc3NpY2FsIEdBTiB0cmFpbmluZzoNCg0KYGBgIHB5dGhvbg0KIyBFeGFtcGxlIG9mIGludGVncmF0aW5nIHF1YW50dW0tZ2VuZXJhdGVkIGRhdGEgd2l0aCBjbGFzc2ljYWwgR0FOIHRyYWluaW5nDQpmb3IgZXBvY2ggaW4gcmFuZ2UoZXBvY2hzKToNCiAgICBmb3IgaSwgZGF0YSBpbiBlbnVtZXJhdGUoZGF0YWxvYWRlcik6DQogICAgICAgICMgUXVhbnR1bSBkYXRhIGdlbmVyYXRpb24NCiAgICAgICAgcWMgPSBxdWFudHVtX2dlbmVyYXRvcigpDQogICAgICAgIGpvYiA9IGV4ZWN1dGUocWMsIGJhY2tlbmQsIHNob3RzPTEpDQogICAgICAgIHJlc3VsdCA9IGpvYi5yZXN1bHQoKQ0KICAgICAgICBxdWFudHVtX2RhdGEgPSBsaXN0KHJlc3VsdC5nZXRfY291bnRzKHFjKS5rZXlzKCkpWzBdDQoNCiAgICAgICAgIyBDb252ZXJ0IHF1YW50dW0gZGF0YSB0byBpbnB1dCBmb3JtYXQNCiAgICAgICAgcXVhbnR1bV9pbnB1dCA9IHRvcmNoLlRlbnNvcihbZmxvYXQoYml0KSBmb3IgYml0IGluIHF1YW50dW1fZGF0YV0pDQoNCiAgICAgICAgIyBDbGFzc2ljYWwgR0FOIHRyYWluaW5nIHN0ZXBzDQogICAgICAgIHJlYWxfZGF0YSA9IGRhdGFbMF0NCiAgICAgICAgZmFrZV9kYXRhID0gRyhxdWFudHVtX2lucHV0KQ0KDQogICAgICAgICMgVXBkYXRlIERpc2NyaW1pbmF0b3INCiAgICAgICAgRC56ZXJvX2dyYWQoKQ0KICAgICAgICByZWFsX291dHB1dCA9IEQocmVhbF9kYXRhKQ0KICAgICAgICBmYWtlX291dHB1dCA9IEQoZmFrZV9kYXRhLmRldGFjaCgpKQ0KICAgICAgICBsb3NzRCA9IGNyaXRlcmlvbihyZWFsX291dHB1dCwgdG9yY2gub25lc19saWtlKHJlYWxfb3V0cHV0KSkgKyBjcml0ZXJpb24oZmFrZV9vdXRwdXQsIHRvcmNoLnplcm9zX2xpa2UoZmFrZV9vdXRwdXQpKQ0KICAgICAgICBsb3NzRC5iYWNrd2FyZCgpDQogICAgICAgIG9wdGltaXplckQuc3RlcCgpDQoNCiAgICAgICAgIyBVcGRhdGUgR2VuZXJhdG9yDQogICAgICAgIEcuemVyb19ncmFkKCkNCiAgICAgICAgZmFrZV9vdXRwdXQgPSBEKGZha2VfZGF0YSkNCiAgICAgICAgbG9zc0cgPSBjcml0ZXJpb24oZmFrZV9vdXRwdXQsIHRvcmNoLm9uZXNfbGlrZShmYWtlX291dHB1dCkpDQogICAgICAgIGxvc3NHLmJhY2t3YXJkKCkNCiAgICAgICAgb3B0aW1pemVyRy5zdGVwKCkNCg0KICAgICAgICAjIFByaW50IGxvc3NlcyAob3B0aW9uYWwpDQogICAgICAgIHByaW50KGYnRXBvY2ggW3tlcG9jaH0ve2Vwb2Noc31dLCBTdGVwIFt7aX0ve2xlbihkYXRhbG9hZGVyKX1dLCBMb3NzIEQ6IHtsb3NzRC5pdGVtKCl9LCBMb3NzIEc6IHtsb3NzRy5pdGVtKCl9JykNCmBgYA0KDQojIyMgU3RlcCA3OiBSdW4gb24gdGhlIEhQQyBTeXN0ZW0NCg0KQWZ0ZXIgc2V0dGluZyB1cCB0aGUgZW52aXJvbm1lbnQgYW5kIHdyaXRpbmcgY29kZSwgeW91IGNhbiBzdWJtaXQgam9iIHRvDQp0aGUgSFBDIHN5c3RlbSB1c2luZyBhIGpvYiBzY2hlZHVsZXIgbGlrZSBTTFVSTSBvciBQQlMuIEhlcmUgaXMgYW4NCmV4YW1wbGUgU0xVUk0gc2NyaXB0Og0KDQpgYGAgc2gNCiMhL2Jpbi9iYXNoDQojU0JBVENIIC0tam9iLW5hbWU9cWdhbl9naXMNCiNTQkFUQ0ggLS1vdXRwdXQ9cWdhbl9naXNfb3V0cHV0LnR4dA0KI1NCQVRDSCAtLW50YXNrcz0xDQojU0JBVENIIC0tY3B1cy1wZXItdGFzaz00DQojU0JBVENIIC0tbWVtPTE2Rw0KI1NCQVRDSCAtLXRpbWU9MjQ6MDA6MDANCiNTQkFUQ0ggLS1wYXJ0aXRpb249Z3B1DQojU0JBVENIIC0tZ3B1cz0xDQoNCm1vZHVsZSBsb2FkIGdjYy8xMy4yLjANCm1vZHVsZSBsb2FkIG52aWRpYS9udmhwYy8yMy43DQptb2R1bGUgbG9hZCBjb25kYQ0KDQpjb25kYSBhY3RpdmF0ZSBxZ2FuX2Vudg0KDQpweXRob24gbXlfcWdhbl9zY3JpcHQucHkNCmBgYA0KDQpTYXZlIHRoaXMgc2NyaXB0IGFzIGBzdWJtaXRfam9iLnNoYCBhbmQgc3VibWl0IGl0IHRvIHRoZSBqb2Igc2NoZWR1bGVyOg0KDQpgYGAgc2gNCnNiYXRjaCBzdWJtaXRfam9iLnNoDQpgYGANCg0KQnkgZm9sbG93aW5nIHRoZXNlIHN0ZXBzLCBJIHNob3VsZCBiZSBhYmxlIHRvIGNyZWF0ZSBhbmQgcnVuIGEgUUdBTiBmb3INCmFub21hbHkgZGV0ZWN0aW9uIGluIEdJUyBvbiBteSBIUEMgc3lzdGVtLCB1dGlsaXppbmcgYm90aCBxdWFudHVtIGFuZA0KY2xhc3NpY2FsIGNvbXB1dGluZyByZXNvdXJjZXMuDQoNCiMgTTMNCg0KVGhlIG1vZHVsZXMgYW5kIGNvbXBpbGVycyBhdmFpbGFibGUgb24gdGhlIEhQQyBzeXN0ZW0gKG0zKSBwcm92aWRlDQp2YXJpb3VzIHRvb2xzIGZvciBzY2llbnRpZmljIGNvbXB1dGluZywgZGV2ZWxvcG1lbnQsIGFuZCBhbmFseXNpcy4NCkhlcmXigJlzIGFuIGV4cGxhbmF0aW9uIG9mIGVhY2ggbW9kdWxlIGFuZCBjb21waWxlciBsaXN0ZWQ6DQoNCiMjIyBBcHBsaWNhdGlvbnMgTW9kdWxlcw0KDQoxLiAgKipSKioNCiAgICAtICAgKipWZXJzaW9uczogNC4yLjMsIDQuMy4wLCA0LjMuMiwgNC40LjAgKEQpKioNCiAgICAtICAgKipSKio6IEEgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UgYW5kIHNvZnR3YXJlIGVudmlyb25tZW50IHVzZWQgZm9yDQogICAgICAgIHN0YXRpc3RpY2FsIGNvbXB1dGluZyBhbmQgZ3JhcGhpY3MuIEl0IGlzIHdpZGVseSB1c2VkIGZvciBkYXRhDQogICAgICAgIGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9uLg0KMi4gICoqYW1wbC8yMDIzMTAzMSoqDQogICAgLSAgICoqQU1QTCoqOiBBIGNvbXByZWhlbnNpdmUgYW5kIHBvd2VyZnVsIGFsZ2VicmFpYyBtb2RlbGluZw0KICAgICAgICBsYW5ndWFnZSBmb3IgbGluZWFyIGFuZCBub25saW5lYXIgb3B0aW1pemF0aW9uIHByb2JsZW1zLg0KMy4gICoqY2hhcm1tL2M0N2IyKioNCiAgICAtICAgKipDSEFSTU0qKjogQSBwcm9ncmFtIGZvciBtYWNyb21vbGVjdWxhciBkeW5hbWljcyBhbmQgbWVjaGFuaWNzDQogICAgICAgIHNpbXVsYXRpb25zLCB1c2VkIGZvciBzdHVkeWluZyB0aGUgYmVoYXZpb3Igb2YgbW9sZWN1bGFyDQogICAgICAgIHN5c3RlbXMuDQo0LiAgKipkeW5hcmUqKg0KICAgIC0gICAqKlZlcnNpb25zOiA1LjQsIDUuNSAoRCkqKg0KICAgIC0gICAqKkR5bmFyZSoqOiBBIHNvZnR3YXJlIHBsYXRmb3JtIGZvciBoYW5kbGluZyBhIHdpZGUgY2xhc3Mgb2YNCiAgICAgICAgZWNvbm9taWMgbW9kZWxzLCBwYXJ0aWN1bGFybHkgdGhvc2UgaW52b2x2aW5nIGR5bmFtaWMgc3RvY2hhc3RpYw0KICAgICAgICBnZW5lcmFsIGVxdWlsaWJyaXVtIChEU0dFKSBhbmQgb3ZlcmxhcHBpbmcgZ2VuZXJhdGlvbnMgKE9MRykNCiAgICAgICAgbW9kZWxzLg0KNS4gICoqZ2F1c3NpYW4qKg0KICAgIC0gICAqKlZlcnNpb25zOiBnMTZjL2hhc3dlbGwsIGcxNmMvemVuMyAoRCkqKg0KICAgIC0gICAqKkdhdXNzaWFuKio6IFNvZnR3YXJlIGZvciBjb21wdXRhdGlvbmFsIGNoZW1pc3RyeSB1c2VkIGZvcg0KICAgICAgICBlbGVjdHJvbmljIHN0cnVjdHVyZSBtb2RlbGluZy4NCjYuICAqKmp1bGlhKioNCiAgICAtICAgKipWZXJzaW9uczogMS44LjUsIDEuOS4xIChEKSoqDQogICAgLSAgICoqSnVsaWEqKjogQSBoaWdoLWxldmVsLCBoaWdoLXBlcmZvcm1hbmNlIHByb2dyYW1taW5nIGxhbmd1YWdlDQogICAgICAgIGZvciB0ZWNobmljYWwgY29tcHV0aW5nLg0KNy4gICoqbmJvKioNCiAgICAtICAgKipWZXJzaW9uczogNy4wL2k0LCA3LjAvaTggKEQpKioNCiAgICAtICAgKipOQk8gKE5hdHVyYWwgQm9uZCBPcmJpdGFsKSoqOiBTb2Z0d2FyZSBmb3Igc3R1ZHlpbmcgY2hlbWljYWwNCiAgICAgICAgYm9uZGluZyBhbmQgZWxlY3Ryb24gZGVuc2l0eS4NCjguICAqKnNuaWQvNS4wKioNCiAgICAtICAgKipTTklEIChTdXBlck5vdmEgSWRlbnRpZmljYXRpb24pKio6IEEgdG9vbCBmb3IgY2xhc3NpZnlpbmcNCiAgICAgICAgc3VwZXJub3ZhIHNwZWN0cmEuDQo5LiAgKip2bWQvMS45LjMqKg0KICAgIC0gICAqKlZNRCAoVmlzdWFsIE1vbGVjdWxhciBEeW5hbWljcykqKjogU29mdHdhcmUgZm9yIG1vbGVjdWxhcg0KICAgICAgICBtb2RlbGluZyBhbmQgYmlvaW5mb3JtYXRpY3MuDQoxMC4gKiphbnN5cy9lbGVjdHJvbmljcyoqDQogICAgLSAgICoqVmVyc2lvbnM6IDIzLjEsIDIzLjIgKEQpKioNCiAgICAtICAgKipBTlNZUyBFbGVjdHJvbmljcyoqOiBTaW11bGF0aW9uIHNvZnR3YXJlIGZvciBlbGVjdHJvbmljcywNCiAgICAgICAgZWxlY3Ryb21hZ25ldGljLCBhbmQgdGhlcm1hbCBhbmFseXNpcy4NCjExLiAqKmNvbXNvbC82LjEqKg0KICAgIC0gICAqKkNPTVNPTCoqOiBBIG11bHRpcGh5c2ljcyBzaW11bGF0aW9uIHNvZnR3YXJlIGZvciBtb2RlbGluZyBhbmQNCiAgICAgICAgc2ltdWxhdGluZyBlbmdpbmVlcmluZyBwcm9ibGVtcy4NCjEyLiAqKmNvbmRhKioNCiAgICAtICAgKipDb25kYSoqOiBBbiBvcGVuLXNvdXJjZSBwYWNrYWdlIG1hbmFnZW1lbnQgYW5kIGVudmlyb25tZW50DQogICAgICAgIG1hbmFnZW1lbnQgc3lzdGVtIHRoYXQgYWxsb3dzIHVzZXJzIHRvIGNyZWF0ZSBpc29sYXRlZA0KICAgICAgICBlbnZpcm9ubWVudHMgYW5kIGluc3RhbGwgcGFja2FnZXMuDQoxMy4gKipjcGxleC8yMi4xLjEqKg0KICAgIC0gICAqKkNQTEVYKio6IE9wdGltaXphdGlvbiBzb2Z0d2FyZSBmb3Igc29sdmluZyBsaW5lYXIgcHJvZ3JhbW1pbmcsDQogICAgICAgIG1peGVkIGludGVnZXIgcHJvZ3JhbW1pbmcsIGFuZCBvdGhlciB0eXBlcyBvZiBvcHRpbWl6YXRpb24NCiAgICAgICAgcHJvYmxlbXMuDQoxNC4gKiplb2cvMy4yOC40KioNCiAgICAtICAgKipFT0cgKEV5ZSBvZiBHTk9NRSkqKjogQW4gaW1hZ2Ugdmlld2VyIGZvciB0aGUgR05PTUUgZGVza3RvcA0KICAgICAgICBlbnZpcm9ubWVudC4NCjE1LiAqKmdlZGl0LzMuMjguMSoqDQogICAgLSAgICoqR2VkaXQqKjogQSB0ZXh0IGVkaXRvciBmb3IgdGhlIEdOT01FIGRlc2t0b3AgZW52aXJvbm1lbnQuDQoxNi4gKipnYXVzc3ZpZXcvNi4wLjE2KioNCiAgICAtICAgKipHYXVzc1ZpZXcqKjogQSBncmFwaGljYWwgaW50ZXJmYWNlIGZvciBHYXVzc2lhbiBzb2Z0d2FyZSwNCiAgICAgICAgYWlkaW5nIGluIHRoZSBzZXR1cCBhbmQgYW5hbHlzaXMgb2YgY29tcHV0YXRpb25hbCBjaGVtaXN0cnkNCiAgICAgICAgY2FsY3VsYXRpb25zLg0KMTcuICoqbWF0aGVtYXRpY2EqKg0KICAgIC0gICAqKlZlcnNpb25zOiAxMy4zLjAsIHIyMDI0YSAoRCkqKg0KICAgIC0gICAqKk1hdGhlbWF0aWNhKio6IEEgY29tcHV0YXRpb25hbCBzb2Z0d2FyZSB1c2VkIGluIHNjaWVudGlmaWMsDQogICAgICAgIGVuZ2luZWVyaW5nLCBtYXRoZW1hdGljYWwgZmllbGRzLCBhbmQgb3RoZXIgYXJlYXMgZm9yDQogICAgICAgIGNvbXB1dGF0aW9ucyBhbmQgdmlzdWFsaXphdGlvbi4NCjE4LiAqKm1hdGxhYioqDQogICAgLSAgICoqVmVyc2lvbnM6IHIyMDIzYSwgcjIwMjRhIChEKSoqDQogICAgLSAgICoqTUFUTEFCKio6IEEgaGlnaC1sZXZlbCBwcm9ncmFtbWluZyBsYW5ndWFnZSBhbmQgZW52aXJvbm1lbnQNCiAgICAgICAgdXNlZCBmb3IgbnVtZXJpY2FsIGNvbXB1dGluZywgZGF0YSBhbmFseXNpcywgYW5kIGFsZ29yaXRobQ0KICAgICAgICBkZXZlbG9wbWVudC4NCjE5LiAqKm9yY2EvNS4wLjQqKg0KICAgIC0gICAqKk9SQ0EqKjogQW4gZWxlY3Ryb25pYyBzdHJ1Y3R1cmUgc29mdHdhcmUgcGFja2FnZSBmb3IgcXVhbnR1bQ0KICAgICAgICBjaGVtaXN0cnkgY2FsY3VsYXRpb25zLg0KMjAuICoqcS1jaGVtKioNCiAgICAtICAgKipWZXJzaW9uczogNS4yLjIsIDYuMC4yIChEKSoqDQogICAgLSAgICoqUS1DaGVtKio6IEEgc29mdHdhcmUgcGFja2FnZSBmb3IgY29tcHV0YXRpb25hbCBjaGVtaXN0cnksDQogICAgICAgIGZvY3VzaW5nIG9uIHF1YW50dW0gY2hlbWljYWwgY2FsY3VsYXRpb25zLg0KMjEuICoqc3RhdGEqKg0KICAgIC0gICAqKlZlcnNpb25zOiBtcC0xNywgbXAtMTggKEQpKioNCiAgICAtICAgKipTdGF0YSoqOiBBIHNvZnR3YXJlIGZvciBzdGF0aXN0aWNzIGFuZCBkYXRhIHNjaWVuY2UsIHVzZWQgZm9yDQogICAgICAgIGRhdGEgYW5hbHlzaXMsIGRhdGEgbWFuYWdlbWVudCwgYW5kIGdyYXBoaWNzLg0KMjIuICoqYWltYWxsLzE5LjEwLjEyKioNCiAgICAtICAgKipBSU1BbGwqKjogU29mdHdhcmUgZm9yIGFuYWx5emluZyBtb2xlY3VsYXIgd2F2ZSBmdW5jdGlvbnMgYW5kDQogICAgICAgIGVsZWN0cm9uIGRlbnNpdHkuDQoyMy4gKiphcHB0YWluZXIvMS4xLjYqKg0KICAgIC0gICAqKkFwcHRhaW5lciAoZm9ybWVybHkgU2luZ3VsYXJpdHkpKio6IEEgY29udGFpbmVyIHBsYXRmb3JtDQogICAgICAgIGRlc2lnbmVkIGZvciBIUEMgZW52aXJvbm1lbnRzLCBhbGxvd2luZyBwb3J0YWJsZSBhbmQNCiAgICAgICAgcmVwcm9kdWNpYmxlIHNvZnR3YXJlIGVudmlyb25tZW50cy4NCjI0LiAqKmJsZW5kZXIvNC4wLjIqKg0KICAgIC0gICAqKkJsZW5kZXIqKjogT3Blbi1zb3VyY2UgM0QgbW9kZWxpbmcsIGFuaW1hdGlvbiwgYW5kIHJlbmRlcmluZw0KICAgICAgICBzb2Z0d2FyZS4NCjI1LiAqKmNmb3VyKioNCiAgICAtICAgKipWZXJzaW9uczogMi4xL21waSwgMi4xL25vbXBpIChEKSoqDQogICAgLSAgICoqQ0ZPVVIqKjogQSBxdWFudHVtIGNoZW1pc3RyeSBwcm9ncmFtIHBhY2thZ2UgZm9yIGhpZ2gtYWNjdXJhY3kNCiAgICAgICAgY2FsY3VsYXRpb25zLg0KMjYuICoqY3J5c3RhbCoqDQogICAgLSAgICoqVmVyc2lvbnM6IDIzLzEuMC4xLCAyMy8xLjAuMS0xIChEKSoqDQogICAgLSAgICoqQ1JZU1RBTCoqOiBTb2Z0d2FyZSBmb3IgY29tcHV0aW5nIHRoZSBlbGVjdHJvbmljIHN0cnVjdHVyZSBvZg0KICAgICAgICBwZXJpb2RpYyBzeXN0ZW1zLg0KMjcuICoqZGVtb24vNi4yLjIqKg0KICAgIC0gICAqKmRlTW9uKio6IEEgc29mdHdhcmUgZm9yIGRlbnNpdHkgZnVuY3Rpb25hbCB0aGVvcnkgKERGVCkNCiAgICAgICAgY2FsY3VsYXRpb25zLg0KMjguICoqZHM5LzguNC4xKioNCiAgICAtICAgKipEUzkqKjogQSB0b29sIGZvciBhc3Ryb25vbWljYWwgZGF0YSB2aXN1YWxpemF0aW9uIGFuZA0KICAgICAgICBhbmFseXNpcy4NCjI5LiAqKmdhbWVzcy8yMDIyLjIqKg0KICAgIC0gICAqKkdBTUVTUyAoR2VuZXJhbCBBdG9taWMgYW5kIE1vbGVjdWxhciBFbGVjdHJvbmljIFN0cnVjdHVyZQ0KICAgICAgICBTeXN0ZW0pKio6IEEgcHJvZ3JhbSBmb3IgY29tcHV0YXRpb25hbCBjaGVtaXN0cnkuDQozMC4gKipndW9yaSoqDQogICAgLSAgICoqVmVyc2lvbnM6IDEwLjAuMSwgMTEuMC4xIChEKSoqDQogICAgLSAgICoqR3Vyb2JpKio6IEFuIG9wdGltaXphdGlvbiBzb2x2ZXIgZm9yIG1hdGhlbWF0aWNhbCBwcm9ncmFtbWluZy4NCjMxLiAqKmlkbC84LjAqKg0KICAgIC0gICAqKklETCAoSW50ZXJhY3RpdmUgRGF0YSBMYW5ndWFnZSkqKjogQSBwcm9ncmFtbWluZyBsYW5ndWFnZSB1c2VkDQogICAgICAgIGZvciBkYXRhIGFuYWx5c2lzLCB2aXN1YWxpemF0aW9uLCBhbmQgY3Jvc3MtcGxhdGZvcm0gYXBwbGljYXRpb24NCiAgICAgICAgZGV2ZWxvcG1lbnQuDQozMi4gKiptb2xkZW4vNi45KioNCiAgICAtICAgKipNb2xkZW4qKjogQSB2aXN1YWxpemF0aW9uIHBhY2thZ2UgZm9yIG1vbGVjdWxhciBhbmQgZWxlY3Ryb25pYw0KICAgICAgICBzdHJ1Y3R1cmUuDQozMy4gKiptb2xwcm8qKg0KICAgIC0gICAqKlZlcnNpb25zOiAyMDIyLjMsIDIwMjQuMSAoRCkqKg0KICAgIC0gICAqKk1vbHBybyoqOiBTb2Z0d2FyZSBmb3IgaGlnaC1hY2N1cmFjeSBlbGVjdHJvbmljIHN0cnVjdHVyZQ0KICAgICAgICBjYWxjdWxhdGlvbnMuDQozNC4gKiptb2pvLzAuNi4xKioNCiAgICAtICAgKipNb2pvKio6IEEgZnJhbWV3b3JrIGZvciBjcmVhdGluZyBtYWNoaW5lIGxlYXJuaW5nIHBpcGVsaW5lcy4NCjM1LiAqKnF1YW50dW1fYXRrKioNCiAgICAtICAgKipWZXJzaW9uczogMjAyMi4wMy1TUDEsIDIwMjIuMTItU1AxIChEKSoqDQogICAgLSAgICoqUXVhbnR1bUFUSyoqOiBBIHNvZnR3YXJlIHBsYXRmb3JtIGZvciBhdG9taWMtc2NhbGUgbW9kZWxpbmcNCiAgICAgICAgYW5kIHNpbXVsYXRpb24uDQozNi4gKipzYXMvOS40bTcqKg0KICAgIC0gICAqKlNBUyoqOiBBIHNvZnR3YXJlIHN1aXRlIGZvciBhZHZhbmNlZCBhbmFseXRpY3MsIGJ1c2luZXNzDQogICAgICAgIGludGVsbGlnZW5jZSwgZGF0YSBtYW5hZ2VtZW50LCBhbmQgcHJlZGljdGl2ZSBhbmFseXRpY3MuDQozNy4gKipzeW5vcHN5cy9waG90b25pY3NvbHV0aW9ucy8yMDIxLjA5LTMqKg0KICAgIC0gICAqKlN5bm9wc3lzIFBob3RvbmljIFNvbHV0aW9ucyoqOiBTb2Z0d2FyZSB0b29scyBmb3IgZGVzaWduaW5nDQogICAgICAgIGFuZCBzaW11bGF0aW5nIHBob3RvbmljIGFuZCBvcHRvZWxlY3Ryb25pYyBjb21wb25lbnRzIGFuZA0KICAgICAgICBzeXN0ZW1zLg0KMzguICoqdGNhZC8yMDIyKioNCiAgICAtICAgKipUQ0FEKio6IFRlY2hub2xvZ3kgQ29tcHV0ZXItQWlkZWQgRGVzaWduIHNvZnR3YXJlIHVzZWQgZm9yDQogICAgICAgIHNpbXVsYXRpbmcgc2VtaWNvbmR1Y3RvciBwcm9jZXNzaW5nIGFuZCBkZXZpY2VzLg0KMzkuICoqdGV4bGl2ZS8yMDIzKioNCiAgICAtICAgKipUZVggTGl2ZSoqOiBBIGNvbXByZWhlbnNpdmUgVGVYIHN5c3RlbSBmb3IgZWRpdGluZyBhbmQNCiAgICAgICAgcHVibGlzaGluZyBkb2N1bWVudHMuDQo0MC4gKip2YXNwLzUuNC40KioNCiAgICAtICAgKipWQVNQIChWaWVubmEgQWIgaW5pdGlvIFNpbXVsYXRpb24gUGFja2FnZSkqKjogQSBzb2Z0d2FyZSBmb3INCiAgICAgICAgYXRvbWljLXNjYWxlIG1hdGVyaWFscyBtb2RlbGluZy4NCg0KIyMjIENvbXBpbGVyIE1vZHVsZXMNCg0KMS4gICoqYW1kLzQuMC4wKioNCiAgICAtICAgKipBTUQgT3B0aW1pemluZyBDL0MrKyBDb21waWxlcioqOiBPcHRpbWl6ZWQgZm9yIEFNRCBwcm9jZXNzb3JzLA0KICAgICAgICBzdXBwb3J0aW5nIEMsIEMrKywgYW5kIEZvcnRyYW4gcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzLg0KMi4gICoqZ2NjKioNCiAgICAtICAgKipWZXJzaW9uczogNi4zLjAsIDExLjIuMCAoRCkqKg0KICAgIC0gICAqKkdDQyAoR05VIENvbXBpbGVyIENvbGxlY3Rpb24pKio6IEEgd2lkZWx5LXVzZWQgY29tcGlsZXINCiAgICAgICAgc3VwcG9ydGluZyBDLCBDKyssIGFuZCBGb3J0cmFuLg0KMy4gICoqaW50ZWwqKg0KICAgIC0gICAqKlZlcnNpb25zOiAyMDIzLjEsIG9uZWFwaS8yMDIzLjIqKg0KICAgIC0gICAqKkludGVsIENvbXBpbGVycyoqOiBPcHRpbWl6ZWQgZm9yIEludGVsIHByb2Nlc3NvcnMsIHN1cHBvcnRpbmcNCiAgICAgICAgaGlnaC1wZXJmb3JtYW5jZSBjb21wdXRpbmcgYW5kIGFwcGxpY2F0aW9ucy4NCjQuICAqKm52aWRpYSoqDQogICAgLSAgICoqVmVyc2lvbnM6IDIxLjMsIDIzLjUgKEQpKioNCiAgICAtICAgKipOVklESUEgSFBDIFNESyoqOiBDb21waWxlcnMgYW5kIHRvb2xzIGZvciBHUFUtYWNjZWxlcmF0ZWQNCiAgICAgICAgYXBwbGljYXRpb25zLCBzdXBwb3J0aW5nIENVREEgYW5kIG90aGVyIHBhcmFsbGVsIHByb2dyYW1taW5nDQogICAgICAgIG1vZGVscy4NCjUuICAqKnJ1c3QvMS43MS4wKioNCiAgICAtICAgKipSdXN0Kio6IEEgc3lzdGVtcyBwcm9ncmFtbWluZyBsYW5ndWFnZSBmb2N1c2VkIG9uIHNhZmV0eSwNCiAgICAgICAgc3BlZWQsIGFuZCBjb25jdXJyZW5jeS4NCg0KIyMjIFVzYWdlIEV4YW1wbGUNCg0KVG8gdXNlIHRoZXNlIG1vZHVsZXMgb24gdGhlIEhQQyBzeXN0ZW0sIHlvdSBuZWVkIHRvIGxvYWQgdGhlIHJlcXVpcmVkDQptb2R1bGVzIGJlZm9yZSBjb21waWxpbmcgb3IgcnVubmluZyBteSBjb2RlLiBIZXJl4oCZcyBhbiBleGFtcGxlIG9mIGhvdyB0bw0KbG9hZCBhIG1vZHVsZSBhbmQgY29tcGlsZSBhIHByb2dyYW06DQoNCmBgYCBzaA0KIyBMb2FkIHRoZSBHQ0MgY29tcGlsZXIgbW9kdWxlDQptb2R1bGUgbG9hZCBnY2MvMTEuMi4wDQoNCiMgQ29tcGlsZSBhIEMgcHJvZ3JhbQ0KZ2NjIC1vIG15X3Byb2dyYW0gbXlfcHJvZ3JhbS5jDQoNCiMgTG9hZCB0aGUgUHl0aG9uIGVudmlyb25tZW50IHdpdGggQ29uZGENCm1vZHVsZSBsb2FkIGNvbmRhDQpjb25kYSBhY3RpdmF0ZSBteV9lbnYNCg0KIyBSdW4gYSBQeXRob24gc2NyaXB0DQpweXRob24gbXlfc2NyaXB0LnB5DQpgYGANCg0KIyBwcm9qZWN0IG9uIG0zDQoNClRvIGRldmVsb3AgYSBRdWFudHVtIEdlbmVyYXRpdmUgQWR2ZXJzYXJpYWwgTmV0d29yayAoUUdBTikgb24gdGhlIE0zIEhQQw0Kc3lzdGVtIGZvciBteSBwcm9qZWN0LCB5b3Ugd2lsbCBuZWVkIHRvIGludGVncmF0ZSBxdWFudHVtIGNvbXB1dGluZyB3aXRoDQpjbGFzc2ljYWwgY29tcHV0aW5nIHRvb2xzLiBIZXJl4oCZcyBhIGNvbXByZWhlbnNpdmUgc3RlcC1ieS1zdGVwIGd1aWRlIHRvDQpoZWxwIHlvdSBnZXQgc3RhcnRlZDoNCg0KIyMjIFN0ZXAgMTogU2V0IFVwIG15IEVudmlyb25tZW50DQoNCiMjIyMgMS4gQ29ubmVjdCB0byB0aGUgTTMgSFBDIFN5c3RlbQ0KDQpVc2UgU1NIIHRvIGNvbm5lY3QgdG8gdGhlIE0zIEhQQyBzeXN0ZW0uDQoNCmBgYCBzaA0Kc3NoIGptY3BoYXVsQG0zbG9naW4wMQ0KYGBgDQoNCiMjIyMgMi4gTG9hZCBOZWNlc3NhcnkgTW9kdWxlcw0KDQpGaXJzdCwgeW91IG5lZWQgdG8gbG9hZCB0aGUgbmVjZXNzYXJ5IG1vZHVsZXMgZm9yIG15IGVudmlyb25tZW50LiBUaGlzDQppbmNsdWRlcyBjb21waWxlcnMsIFB5dGhvbiBlbnZpcm9ubWVudHMsIGFuZCBhbnkgcmVsZXZhbnQgYXBwbGljYXRpb25zLg0KDQpgYGAgc2gNCm1vZHVsZSBsb2FkIGdjYy8xMS4yLjANCm1vZHVsZSBsb2FkIGNvbmRhDQptb2R1bGUgbG9hZCBudmlkaWEvMjMuNQ0KYGBgDQoNCiMjIyBTdGVwIDI6IENyZWF0ZSBhbmQgQWN0aXZhdGUgYSBDb25kYSBFbnZpcm9ubWVudA0KDQpVc2UgQ29uZGEgdG8gY3JlYXRlIGFuIGlzb2xhdGVkIGVudmlyb25tZW50IGZvciBteSBwcm9qZWN0LCB3aGljaCB3aWxsDQppbmNsdWRlIHRoZSBuZWNlc3NhcnkgUHl0aG9uIHBhY2thZ2VzIGZvciBib3RoIGNsYXNzaWNhbCBhbmQgcXVhbnR1bQ0KY29tcHV0aW5nLg0KDQpgYGAgc2gNCmNvbmRhIGNyZWF0ZSAtbiBxZ2FuX2VudiBweXRob249My45DQpjb25kYSBhY3RpdmF0ZSBxZ2FuX2Vudg0KYGBgDQoNCiMjIyBTdGVwIDM6IEluc3RhbGwgUmVxdWlyZWQgUHl0aG9uIFBhY2thZ2VzDQoNCldpdGggbXkgQ29uZGEgZW52aXJvbm1lbnQgYWN0aXZhdGVkLCBpbnN0YWxsIHRoZSBuZWNlc3NhcnkgUHl0aG9uDQpwYWNrYWdlcyBmb3IgcXVhbnR1bSBjb21wdXRpbmcsIG1hY2hpbmUgbGVhcm5pbmcsIGFuZCBHSVMgYXBwbGljYXRpb25zLg0KDQpgYGAgc2gNCiMgSW5zdGFsbCBiYXNpYyBwYWNrYWdlcyBmb3IgR0lTIGFuZCBxdWFudHVtIGNvbXB1dGluZw0KY29uZGEgaW5zdGFsbCBudW1weSBwYW5kYXMgZ2VvcGFuZGFzIGdkYWwgbWF0cGxvdGxpYg0KcGlwIGluc3RhbGwgcWlza2l0IHRlbnNvcmZsb3cgdG9yY2ggdG9yY2h2aXNpb24NCmBgYA0KDQojIyMgU3RlcCA0OiBEZXZlbG9wIHRoZSBRR0FODQoNCllvdSB3aWxsIGNyZWF0ZSB0aGUgUUdBTiBieSBpbnRlZ3JhdGluZyBib3RoIGNsYXNzaWNhbCBhbmQgcXVhbnR1bQ0KY29tcG9uZW50cy4gQmVsb3cgaXMgYW4gZXhhbXBsZSBzZXR1cC4NCg0KIyMjIyBDbGFzc2ljYWwgR0FOIGZvciBHSVMgRGF0YSAodXNpbmcgUHlUb3JjaCkNCg0KQ3JlYXRlIGFuZCB0cmFpbiBhIGNsYXNzaWNhbCBHQU4gZm9yIGdlbmVyYXRpbmcgYW5kIGFuYWx5emluZyBHSVMgZGF0YS4NCg0KKipHQU4gQ29tcG9uZW50cyoqDQoNCmBgYCBweXRob24NCmltcG9ydCB0b3JjaA0KaW1wb3J0IHRvcmNoLm5uIGFzIG5uDQppbXBvcnQgdG9yY2gub3B0aW0gYXMgb3B0aW0NCg0KY2xhc3MgR2VuZXJhdG9yKG5uLk1vZHVsZSk6DQogICAgZGVmIF9faW5pdF9fKHNlbGYpOg0KICAgICAgICBzdXBlcihHZW5lcmF0b3IsIHNlbGYpLl9faW5pdF9fKCkNCiAgICAgICAgc2VsZi5tYWluID0gbm4uU2VxdWVudGlhbCgNCiAgICAgICAgICAgIG5uLkxpbmVhcigxMDAsIDI1NiksDQogICAgICAgICAgICBubi5SZUxVKCksDQogICAgICAgICAgICBubi5MaW5lYXIoMjU2LCA1MTIpLA0KICAgICAgICAgICAgbm4uUmVMVSgpLA0KICAgICAgICAgICAgbm4uTGluZWFyKDUxMiwgMTAyNCksDQogICAgICAgICAgICBubi5UYW5oKCkNCiAgICAgICAgKQ0KICAgIA0KICAgIGRlZiBmb3J3YXJkKHNlbGYsIGlucHV0KToNCiAgICAgICAgcmV0dXJuIHNlbGYubWFpbihpbnB1dCkNCg0KY2xhc3MgRGlzY3JpbWluYXRvcihubi5Nb2R1bGUpOg0KICAgIGRlZiBfX2luaXRfXyhzZWxmKToNCiAgICAgICAgc3VwZXIoRGlzY3JpbWluYXRvciwgc2VsZikuX19pbml0X18oKQ0KICAgICAgICBzZWxmLm1haW4gPSBubi5TZXF1ZW50aWFsKA0KICAgICAgICAgICAgbm4uTGluZWFyKDEwMjQsIDUxMiksDQogICAgICAgICAgICBubi5MZWFreVJlTFUoMC4yKSwNCiAgICAgICAgICAgIG5uLkxpbmVhcig1MTIsIDI1NiksDQogICAgICAgICAgICBubi5MZWFreVJlTFUoMC4yKSwNCiAgICAgICAgICAgIG5uLkxpbmVhcigyNTYsIDEpLA0KICAgICAgICAgICAgbm4uU2lnbW9pZCgpDQogICAgICAgICkNCiAgICANCiAgICBkZWYgZm9yd2FyZChzZWxmLCBpbnB1dCk6DQogICAgICAgIHJldHVybiBzZWxmLm1haW4oaW5wdXQpDQoNCiMgSW5pdGlhbGl6ZSBtb2RlbHMNCkcgPSBHZW5lcmF0b3IoKQ0KRCA9IERpc2NyaW1pbmF0b3IoKQ0KDQojIE9wdGltaXplcnMgYW5kIGxvc3MgZnVuY3Rpb24NCm9wdGltaXplckcgPSBvcHRpbS5BZGFtKEcucGFyYW1ldGVycygpLCBscj0wLjAwMDIpDQpvcHRpbWl6ZXJEID0gb3B0aW0uQWRhbShELnBhcmFtZXRlcnMoKSwgbHI9MC4wMDAyKQ0KY3JpdGVyaW9uID0gbm4uQkNFTG9zcygpDQoNCiMgRHVtbXkgdHJhaW5pbmcgbG9vcCBwbGFjZWhvbGRlcg0KZXBvY2hzID0gMTAwDQpmb3IgZXBvY2ggaW4gcmFuZ2UoZXBvY2hzKToNCiAgICAjIFRyYWluaW5nIGNvZGUgaGVyZQ0KICAgIHBhc3MNCmBgYA0KDQojIyMjIEludGVncmF0ZSBRdWFudHVtIENvbXBvbmVudCB1c2luZyBRaXNraXQNCg0KQWRkIHRoZSBxdWFudHVtIGNvbXBvbmVudCB0byBteSBHQU4gdXNpbmcgUWlza2l0IGZvciBxdWFudHVtIGRhdGENCmdlbmVyYXRpb24uDQoNCmBgYCBweXRob24NCmZyb20gcWlza2l0IGltcG9ydCBRdWFudHVtQ2lyY3VpdCwgQWVyLCBleGVjdXRlDQpmcm9tIHFpc2tpdC52aXN1YWxpemF0aW9uIGltcG9ydCBwbG90X2hpc3RvZ3JhbQ0KDQpkZWYgcXVhbnR1bV9nZW5lcmF0b3IoKToNCiAgICBxYyA9IFF1YW50dW1DaXJjdWl0KDEsIDEpDQogICAgcWMuaCgwKQ0KICAgIHFjLm1lYXN1cmUoMCwgMCkNCiAgICByZXR1cm4gcWMNCg0KYmFja2VuZCA9IEFlci5nZXRfYmFja2VuZCgncWFzbV9zaW11bGF0b3InKQ0KcWMgPSBxdWFudHVtX2dlbmVyYXRvcigpDQpqb2IgPSBleGVjdXRlKHFjLCBiYWNrZW5kLCBzaG90cz0xMDAwKQ0KcmVzdWx0ID0gam9iLnJlc3VsdCgpDQpjb3VudHMgPSByZXN1bHQuZ2V0X2NvdW50cyhxYykNCnByaW50KGNvdW50cykNCg0KcGxvdF9oaXN0b2dyYW0oY291bnRzKQ0KYGBgDQoNCiMjIyBTdGVwIDU6IEludGVncmF0ZSBDbGFzc2ljYWwgYW5kIFF1YW50dW0gQ29tcG9uZW50cyBpbiB0aGUgUUdBTg0KDQpDb21iaW5lIHF1YW50dW0tZ2VuZXJhdGVkIGRhdGEgd2l0aCBjbGFzc2ljYWwgR0FOIHRyYWluaW5nOg0KDQpgYGAgcHl0aG9uDQppbXBvcnQgZ2VvcGFuZGFzIGFzIGdwZA0KaW1wb3J0IG51bXB5IGFzIG5wDQoNCiMgRXhhbXBsZSBvZiBpbnRlZ3JhdGluZyBxdWFudHVtLWdlbmVyYXRlZCBkYXRhIHdpdGggY2xhc3NpY2FsIEdBTiB0cmFpbmluZw0KZm9yIGVwb2NoIGluIHJhbmdlKGVwb2Nocyk6DQogICAgZm9yIGksIGRhdGEgaW4gZW51bWVyYXRlKGRhdGFsb2FkZXIpOg0KICAgICAgICAjIFF1YW50dW0gZGF0YSBnZW5lcmF0aW9uDQogICAgICAgIHFjID0gcXVhbnR1bV9nZW5lcmF0b3IoKQ0KICAgICAgICBqb2IgPSBleGVjdXRlKHFjLCBiYWNrZW5kLCBzaG90cz0xKQ0KICAgICAgICByZXN1bHQgPSBqb2IucmVzdWx0KCkNCiAgICAgICAgcXVhbnR1bV9kYXRhID0gbGlzdChyZXN1bHQuZ2V0X2NvdW50cyhxYykua2V5cygpKVswXQ0KDQogICAgICAgICMgQ29udmVydCBxdWFudHVtIGRhdGEgdG8gaW5wdXQgZm9ybWF0DQogICAgICAgIHF1YW50dW1faW5wdXQgPSB0b3JjaC5UZW5zb3IoW2Zsb2F0KGJpdCkgZm9yIGJpdCBpbiBxdWFudHVtX2RhdGFdKQ0KDQogICAgICAgICMgQ2xhc3NpY2FsIEdBTiB0cmFpbmluZyBzdGVwcw0KICAgICAgICByZWFsX2RhdGEgPSBkYXRhWzBdDQogICAgICAgIGZha2VfZGF0YSA9IEcocXVhbnR1bV9pbnB1dCkNCg0KICAgICAgICAjIFVwZGF0ZSBEaXNjcmltaW5hdG9yDQogICAgICAgIEQuemVyb19ncmFkKCkNCiAgICAgICAgcmVhbF9vdXRwdXQgPSBEKHJlYWxfZGF0YSkNCiAgICAgICAgZmFrZV9vdXRwdXQgPSBEKGZha2VfZGF0YS5kZXRhY2goKSkNCiAgICAgICAgbG9zc0QgPSBjcml0ZXJpb24ocmVhbF9vdXRwdXQsIHRvcmNoLm9uZXNfbGlrZShyZWFsX291dHB1dCkpICsgY3JpdGVyaW9uKGZha2Vfb3V0cHV0LCB0b3JjaC56ZXJvc19saWtlKGZha2Vfb3V0cHV0KSkNCiAgICAgICAgbG9zc0QuYmFja3dhcmQoKQ0KICAgICAgICBvcHRpbWl6ZXJELnN0ZXAoKQ0KDQogICAgICAgICMgVXBkYXRlIEdlbmVyYXRvcg0KICAgICAgICBHLnplcm9fZ3JhZCgpDQogICAgICAgIGZha2Vfb3V0cHV0ID0gRChmYWtlX2RhdGEpDQogICAgICAgIGxvc3NHID0gY3JpdGVyaW9uKGZha2Vfb3V0cHV0LCB0b3JjaC5vbmVzX2xpa2UoZmFrZV9vdXRwdXQpKQ0KICAgICAgICBsb3NzRy5iYWNrd2FyZCgpDQogICAgICAgIG9wdGltaXplckcuc3RlcCgpDQoNCiAgICAgICAgIyBQcmludCBsb3NzZXMgKG9wdGlvbmFsKQ0KICAgICAgICBwcmludChmJ0Vwb2NoIFt7ZXBvY2h9L3tlcG9jaHN9XSwgU3RlcCBbe2l9L3tsZW4oZGF0YWxvYWRlcil9XSwgTG9zcyBEOiB7bG9zc0QuaXRlbSgpfSwgTG9zcyBHOiB7bG9zc0cuaXRlbSgpfScpDQpgYGANCg0KIyMjIFN0ZXAgNjogUHJlcGFyZSBHSVMgRGF0YSBmb3IgVHJhaW5pbmcNCg0KTG9hZCBhbmQgcHJlcHJvY2VzcyBHSVMgZGF0YSBmb3IgdXNlIGluIHRoZSBRR0FOLg0KDQpgYGAgcHl0aG9uDQojIExvYWQgR0lTIGRhdGEgdXNpbmcgR2VvUGFuZGFzDQpnaXNfZGF0YSA9IGdwZC5yZWFkX2ZpbGUoJ3BhdGhfdG9fbXlfZ2lzX2RhdGEuc2hwJykNCg0KIyBQcmVwcm9jZXNzIEdJUyBkYXRhDQojIEV4YW1wbGU6IG5vcm1hbGl6ZSBkYXRhLCBjb252ZXJ0IHRvIHRlbnNvciwgZXRjLg0KZ2lzX2RhdGEgPSBnaXNfZGF0YS50b19jcnMoZXBzZz00MzI2KSAgIyBDb252ZXJ0IHRvIGEgY29tbW9uIGNvb3JkaW5hdGUgcmVmZXJlbmNlIHN5c3RlbQ0KZGF0YV90ZW5zb3IgPSB0b3JjaC50ZW5zb3IoZ2lzX2RhdGEudmFsdWVzLCBkdHlwZT10b3JjaC5mbG9hdDMyKQ0KYGBgDQoNCiMjIyBTdGVwIDc6IFRyYWluIGFuZCBFdmFsdWF0ZSB0aGUgUUdBTg0KDQpSdW4gdGhlIHRyYWluaW5nIHByb2Nlc3MgYW5kIGV2YWx1YXRlIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlLg0KDQpgYGAgcHl0aG9uDQojIFRyYWluaW5nIGFuZCBldmFsdWF0aW9uIGxvb3ANCmZvciBlcG9jaCBpbiByYW5nZShlcG9jaHMpOg0KICAgIGZvciBpLCBkYXRhIGluIGVudW1lcmF0ZShkYXRhbG9hZGVyKToNCiAgICAgICAgIyBRdWFudHVtIGFuZCBjbGFzc2ljYWwgZGF0YSBpbnRlZ3JhdGlvbiBhbmQgdHJhaW5pbmcNCiAgICAgICAgIyBteSB0cmFpbmluZyBjb2RlIGhlcmUuLi4NCg0KIyBTYXZlIHRoZSBtb2RlbA0KdG9yY2guc2F2ZShHLnN0YXRlX2RpY3QoKSwgJ2dlbmVyYXRvcl9tb2RlbC5wdGgnKQ0KdG9yY2guc2F2ZShELnN0YXRlX2RpY3QoKSwgJ2Rpc2NyaW1pbmF0b3JfbW9kZWwucHRoJykNCg0KIyBFdmFsdWF0ZSB0aGUgbW9kZWwNCiMgbXkgZXZhbHVhdGlvbiBjb2RlIGhlcmUuLi4NCmBgYA0KDQojIyMgU3RlcCA4OiBSdW4gb24gdGhlIEhQQyBTeXN0ZW0NCg0KU3VibWl0am9iIHRvIHRoZSBIUEMgc3lzdGVtIHVzaW5nIGEgam9iIHNjaGVkdWxlciBsaWtlIFNMVVJNIG9yIFBCUy4NCkhlcmUgaXMgYW4gZXhhbXBsZSBTTFVSTSBzY3JpcHQ6DQoNCmBgYCBzaA0KIyEvYmluL2Jhc2gNCiNTQkFUQ0ggLS1qb2ItbmFtZT1xZ2FuX2dpcw0KI1NCQVRDSCAtLW91dHB1dD1xZ2FuX2dpc19vdXRwdXQudHh0DQojU0JBVENIIC0tbnRhc2tzPTENCiNTQkFUQ0ggLS1jcHVzLXBlci10YXNrPTQNCiNTQkFUQ0ggLS1tZW09MTZHDQojU0JBVENIIC0tdGltZT0yNDowMDowMA0KI1NCQVRDSCAtLXBhcnRpdGlvbj1ncHUNCiNTQkFUQ0ggLS1ncHVzPTENCg0KbW9kdWxlIGxvYWQgZ2NjLzExLjIuMA0KbW9kdWxlIGxvYWQgY29uZGENCm1vZHVsZSBsb2FkIG52aWRpYS8yMy41DQoNCmNvbmRhIGFjdGl2YXRlIHFnYW5fZW52DQoNCnB5dGhvbiBteV9xZ2FuX3NjcmlwdC5weQ0KYGBgDQoNClNhdmUgdGhpcyBzY3JpcHQgYXMgYHN1Ym1pdF9qb2Iuc2hgIGFuZCBzdWJtaXQgaXQgdG8gdGhlIGpvYiBzY2hlZHVsZXI6DQoNCmBgYCBzaA0Kc2JhdGNoIHN1Ym1pdF9qb2Iuc2gNCmBgYA0KDQpbSFBDDQpjb25kYV0oaHR0cHM6Ly9zb3V0aGVybm1ldGhvZGlzdHVuaXZlcnNpdHkuZ2l0aHViLmlvL1NNVV9TdXBlclBPRF8xMDEvMDItV29ya2luZyUyMHdpdGglMjBDb25kYS9pbmRleC5odG1sKQ0KDQoNCiMjDQoNCm5hbWU6IGVzc2VudGlhbF9lbnYNCmNoYW5uZWxzOg0KICAtIGRlZmF1bHRzDQogIC0gY29uZGEtZm9yZ2UNCmRlcGVuZGVuY2llczoNCiAgLSBweXRob249My4xMA0KICAtIG51bXB5DQogIC0gcGFuZGFzDQogIC0gbWF0cGxvdGxpYg0KICAtIHNjaXB5DQogIC0gc2Npa2l0LWxlYXJuDQogIC0ganVweXRlcg0KICAtIHBpcA0KICAtIHBpcDoNCiAgICAgIC0gcWlza2l0DQogICAgICAtIHB5dG9yY2gNCiAgICAgIC0gdG9yY2h2aXNpb24NCiAgICAgIC0gdGVuc29yZmxvdw0KICAgICAgLSBrZXJhcw0KICAgICAgLSBwbG90bHkNCiAgICAgIC0gc2VhYm9ybg0KDQoNCmNvbmRhIGVudiBjcmVhdGUgLWYgZW52aXJvbm1lbnQzLnltbA0KDQojIyMgcGlwIHVuaW5zdGFsbCB1bHRyYWx5dGljcyBydXN0d29ya3ggbnVtcHkgICAgIHBpcCBpbnN0YWxsIHVsdHJhbHl0aWNzIHJ1c3R3b3JreD09MC4xNC4yDQoNCg0KIyMjIGNvbmRhIGVudiBleHBvcnQgPiBlbnZpcm9ubWVudC55bWwNCiMjIyBjb25kYSB1cGRhdGUgLS1hbGwgLS1kcnktcnVuDQoNCmBgYHtweXRob259DQpjb25kYSBjcmVhdGUgLW4gbmV3X2Vudl9uYW1lIHB5dGhvbj0zLjEwDQpjb25kYSBhY3RpdmF0ZSBuZXdfZW52X25hbWUNCmNvbmRhIGluc3RhbGwgbnVtcHkgc2NpcHkgbWF0cGxvdGxpYiBzZWFib3JuIC4uLg0KYGBgDQoNCmBgYHtzaH0NCmNvbmRhIGVudiBleHBvcnQgPiBlbnZpcm9ubWVudF9uYW1lLnltbA0KYGBgDQoNCg0KYGBge3lhbWx9DQpkZXBlbmRlbmNpZXM6DQogIC0gbnVtcHk9MS4yNi40DQogIC0gc2NpcHk9MS4xNC4wDQogIC0gbWF0cGxvdGxpYj0zLjguNA0KICAtIHNlYWJvcm49MC4xMy4yDQogIC0gLi4uDQpgYGANCg0KYGB7c2h9DQpjb25kYSBlbnYgY3JlYXRlIC1mIGVudmlyb25tZW50X25hbWUueW1sDQpgYGANCg0KYGBge3NofQ0KY29uZGEgY29uZmlnIC0tYWRkIGNoYW5uZWxzIGNvbmRhLWZvcmdlDQpjb25kYSBjb25maWcgLS1zZXQgY2hhbm5lbF9wcmlvcml0eSBzdHJpY3QNCmBgYA0KDQoNCiMjIG5hbm8gZW52aXJvbm1lbnQueW1sIC0gc2F2ZSBhcyBuZXcgIA0KDQpgYGB7eWFtbH0NCm5hbWU6IG15X25ld19lbnYNCmNoYW5uZWxzOg0KICAtIGNvbmRhLWZvcmdlDQogIC0gZGVmYXVsdHMNCmRlcGVuZGVuY2llczoNCiAgLSBweXRob249My4xMA0KICAtIG51bXB5PTEuMjYuNA0KICAtIHNjaXB5PTEuMTQuMA0KICAtIG1hdHBsb3RsaWI9My44LjQNCiAgLSBzZWFib3JuPTAuMTMuMg0KICAtIHBhbmRhcz0yLjIuMg0KICAtIHRxZG09NC42Ni40DQogIC0ganVweXRlcg0KICAtIGlweWtlcm5lbA0KICAtIHNjaWtpdC1sZWFybj0xLjUuMA0KICAtIHB5dG9yY2g9Mi4zLjENCiAgLSB0b3JjaHZpc2lvbj0wLjE4LjENCiAgLSBxaXNraXQ9MS4xLjENCiAgLSBweXVuaWNvcm4NCiAgLSB0ZW5zb3JmbG93DQogIC0ga2VyYXMNCiAgLSBwbG90bHkNCiAgLSBoNXB5DQogIC0gaDVuZXRjZGYNCiAgLSBmb2xpdW0NCiAgLSBjYXJ0b3B5DQogIC0gcHktb3BlbmN2DQogIC0gb3BlbmN2DQpgYGANCg0KDQojIyNjb25kYSBlbnYgZXhwb3J0ID4gZW52aXJvbm1lbnRfbmFtZS55bWwNCg0KIyMjIG1vZHVsZSBsb2FkIGdjYy8xMS4yLjANCiMjIyBtb2R1bGUgbG9hZCBjb25kYQ0KDQoNCmBgYHtzc2h9DQpzYWxsb2MgLS1ub2Rlcz0xIC0tbnRhc2tzPTEgLS1jcHVzLXBlci10YXNrPTQgLS1tZW09MTZHIC0tdGltZT0wMjowMDowMCAtLXBhcnRpdGlvbj1iYXRjaCAtLWdyZXM9Z3B1OjENCg0KDQoNCm1vZHVsZSBsb2FkIGdjYy8xMS4yLjANCm1vZHVsZSBsb2FkIGNvbmRhDQpjb25kYSBhY3RpdmF0ZSBxZ2FuX2Vudg0KDQpGdWxsIENvbW1hbmQgU2VxdWVuY2UNCkFsbG9jYXRlIHJlc291cmNlcyBvbiB0aGUgSFBDIHN5c3RlbToNCg0Kc2gNCkNvcHkgY29kZQ0Kc2FsbG9jIC0tbm9kZXM9MSAtLW50YXNrcz0xIC0tY3B1cy1wZXItdGFzaz00IC0tbWVtPTE2RyAtLXRpbWU9MDI6MDA6MDAgLS1wYXJ0aXRpb249YmF0Y2ggLS1ncmVzPWdwdToxDQpMb2FkIG5lY2Vzc2FyeSBtb2R1bGVzIGFuZCBhY3RpdmF0ZSBDb25kYSBlbnZpcm9ubWVudDoNCg0Kc2gNCkNvcHkgY29kZQ0KbW9kdWxlIGxvYWQgZ2NjLzExLjIuMA0KbW9kdWxlIGxvYWQgY29uZGENCmNvbmRhIGFjdGl2YXRlIHFnYW5fZW52DQpjb25kYSBpbnN0YWxsIGp1cHl0ZXINClN0YXJ0IEp1cHl0ZXIgTm90ZWJvb2s6DQoNCnNoDQpDb3B5IGNvZGUNCmp1cHl0ZXItbm90ZWJvb2sgLS1uby1icm93c2VyIC0tcG9ydD04ODg4DQpJZiB0aGlzIGZhaWxzLCB0cnkgSnVweXRlckxhYjoNCg0Kc2gNCkNvcHkgY29kZQ0KanVweXRlciBsYWIgLS1uby1icm93c2VyIC0tcG9ydD04ODg4DQpTZXQgdXAgU1NIIHR1bm5lbGluZyBvbiB5b3VyIGxvY2FsIG1hY2hpbmU6DQoNCnNoDQpDb3B5IGNvZGUNCnNzaCAtTiAtTCA4ODg4OmxvY2FsaG9zdDo4ODg4IGptY3BoYXVsQHNsb2dpbi0wMg0KQWNjZXNzIEp1cHl0ZXIgTm90ZWJvb2sgdmlhIHlvdXIgd2ViIGJyb3dzZXI6DQoNCnJ1YnkNCkNvcHkgY29kZQ0KaHR0cDovL2xvY2FsaG9zdDo4ODg4Lz90b2tlbj1ZT1VSX1RPS0VODQoNCg0KYGBg