Libraries

import requests
import pyreadstat
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import seaborn as sns
from scipy.stats import pearsonr
import pingouin as pg
from scipy.stats import kendalltau
from scipy.stats import pointbiserialr
from scipy.stats import chi2_contingency
import statsmodels.api as sm
from statsmodels.graphics.mosaicplot import mosaic

Downloading the file

#import requests

# URL and destination file name for downloading the data
url = "https://github.com/kflisikowski/ds/blob/master/bank_defaults.sav?raw=true"
destfile = "bank_defaults.sav"

# Download the file
response = requests.get(url)
if response.status_code == 200:
    with open(destfile, 'wb') as file:
        file.write(response.content)
    print("File downloaded successfully.")
else:
    print("Failed to download the file. Status code:", response.status_code)
## 53033
## File downloaded successfully.

Reading the .sav file on Python

# import pyreadstat

bank_defaults, meta = pyreadstat.read_sav(destfile)

Dropping missing values

#import pandas as pd

bank = bank_defaults.dropna()

Converting default column to a categorical type.

#Also we’re checking the data with .head() & .dtypes functions / instances from Python.

# Convert 'default' column to a categorical type
bank['def'] = bank['default'].astype('category')
## <string>:3: SettingWithCopyWarning: 
## A value is trying to be set on a copy of a slice from a DataFrame.
## Try using .loc[row_indexer,col_indexer] = value instead
## 
## See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
# Print the first few rows and data types of the DataFrame
print(bank.head())
##     age   ed  employ  address  ...  preddef1  preddef2  preddef3  def
## 0  41.0  3.0    17.0     12.0  ...  0.808394  0.788640  0.213043  1.0
## 1  27.0  1.0    10.0      6.0  ...  0.198297  0.128445  0.436903  0.0
## 2  40.0  1.0    15.0     14.0  ...  0.010036  0.002987  0.141023  0.0
## 3  41.0  1.0    15.0     14.0  ...  0.022138  0.010273  0.104422  0.0
## 4  24.0  2.0     2.0      0.0  ...  0.781588  0.737885  0.436903  1.0
## 
## [5 rows x 13 columns]
print(bank.dtypes)
## age          float64
## ed           float64
## employ       float64
## address      float64
## income       float64
## debtinc      float64
## creddebt     float64
## othdebt      float64
## default      float64
## preddef1     float64
## preddef2     float64
## preddef3     float64
## def         category
## dtype: object

#We can see the plot:

# Scatter plot of original data
plt.figure(figsize=(7, 6))
plt.scatter(bank['income'], bank['debtinc'], label='Data Points')


# Add title and axis labels
plt.title("Income vs. Debt Income")
plt.xlabel("Income")
plt.ylabel("Debt Income")

# Show the plot
plt.show()

Applying logarithmic transformation:

#import numpy as np

#Apply logarithmic transformation
bank.loc[:, 'log_income'] = np.log(bank.loc[:, 'income'])
## <string>:4: SettingWithCopyWarning: 
## A value is trying to be set on a copy of a slice from a DataFrame.
## Try using .loc[row_indexer,col_indexer] = value instead
## 
## See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
bank.loc[:, 'log_debtinc'] = np.log(bank.loc[:, 'debtinc'])
## <string>:1: SettingWithCopyWarning: 
## A value is trying to be set on a copy of a slice from a DataFrame.
## Try using .loc[row_indexer,col_indexer] = value instead
## 
## See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

Normalizing the skewed distribution of incomes using log:

# Scatter plot of original data
plt.figure(figsize=(7, 6))
plt.scatter(bank['log_income'], bank['log_debtinc'], label='Data Points')


# Add title and axis labels
plt.title("Log Income vs. Log Debt Income")
plt.xlabel("Log Income")
plt.ylabel("Log Debt Income")

# Show the plot
plt.show()

Creating a scatter plot with the logarithmically transformed data

# Scatter plot of logarithmically transformed data
plt.figure(figsize=(7, 6))
plt.scatter(bank['log_income'], bank['log_debtinc'], label='Data Points')

# Perform linear regression on logarithmically transformed data
slope_log, intercept_log, r_value_log, p_value_log, std_err_log = stats.linregress(bank['log_income'], bank['log_debtinc'])

# Generate values for the regression line
x_log = np.linspace(bank['log_income'].min(), bank['log_income'].max(), 100)
y_log = slope_log * x_log + intercept_log

# Plot the regression line
plt.plot(x_log, y_log, color='red', label='Regression Line')

# Add title and axis labels
plt.title("Logarithmic Income vs. Logarithmic Debt Income with Regression Line")
plt.xlabel("Logarithmic Income")
plt.ylabel("Logarithmic Debt Income")

# Add a legend
plt.legend()

# Show the plot
plt.show()

Scatterplots by groups

# Grouping the data by education level
grouped_data = bank.groupby('ed')

# Creating separate scatterplots for each education level
for name, group in grouped_data:
    plt.figure(figsize=(7, 6))
    plt.scatter(group['log_income'], group['log_debtinc'], label='Data Points')
    
    # Perform linear regression
    slope, intercept, r_value, p_value, std_err = stats.linregress(group['log_income'], group['log_debtinc'])
    
    # Generate values for the regression line
    x = np.linspace(group['log_income'].min(), group['log_income'].max(), 100)
    y = slope * x + intercept
    
    # Plot the regression line
    plt.plot(x, y, color='red', label='Regression Line')
    
    # Add title and axis labels
    plt.title(f"Logarithmic Income vs. Logarithmic Debt Income (Education Level: {name})")
    plt.xlabel("Logarithmic Income")
    plt.ylabel("Logarithmic Debt Income")
    
    # Add a legend
    plt.legend()
    
    # Show the plot
    plt.show()

Estimated Density Plotes

#To see more closely if there any differences between those two distributions adding their estimated density plots

# Scatter plot with color by education level
plt.figure(figsize=(10, 6))
sns.scatterplot(x=bank['income'], y=bank['debtinc'], hue=bank['ed'], palette='viridis')

# Add title and axis labels
plt.title("Income vs. Debt Income")
plt.xlabel("Income")
plt.ylabel("Debt Income")

# Show the plot
plt.show()

# Marginal density plot of age
plt.figure(figsize=(8, 6))
sns.histplot(bank['age'], kde=True)

# Add title and axis labels
plt.title("Density Plot of Age")
plt.xlabel("Age")
plt.ylabel("Density")

# Show the plot
plt.show()

# Marginal density plot of y
plt.figure(figsize=(8, 6))
sns.histplot(bank_defaults['age'], kde=True)

# Add title and axis labels
plt.title("Density Plot of Y")
plt.xlabel("Y")
plt.ylabel("Density")

# Show the plot
plt.show()

Plots Together:

import matplotlib.pyplot as plt
import seaborn as sns

# Set up the layout
plt.figure(figsize=(12, 10))  # Adjust the figure size as needed

# Scatter plot
plt.subplot(2, 2, 1)
sns.scatterplot(x=bank['income'], y=bank['debtinc'], hue=bank['ed'], palette='viridis')
plt.title("Income vs. Debt Income")
plt.xlabel("Income")
plt.ylabel("Debt Income")



# Marginal density plot of age (top panel)
plt.subplot(2, 2, 2)
sns.histplot(bank['age'], kde=True)
plt.title("Density Plot of Age")
plt.xlabel("Age")
plt.ylabel("Density")

# Create space for the third plot
plt.subplot(2, 2, 3)
plt.axis('off')  # Turn off the axis for this subplot
## (0.0, 1.0, 0.0, 1.0)
# Marginal density plot of y (right panel)
plt.subplot(2, 2, 4)
sns.histplot(bank_defaults['age'], kde=True)
plt.title("Density Plot of Y")
plt.xlabel("Y")
plt.ylabel("Density")

# Adjust layout
plt.tight_layout()

# Show the plot
plt.show()

##Giving Density Curves to Scatterplots

# Scatter plot
plt.figure(figsize=(10, 6))
plt.scatter(bank['income'], bank['debtinc'], c=bank['ed'], cmap='viridis')
plt.title("Income vs. Debt Income")
plt.xlabel("Income")
plt.ylabel("Debt Income")

# Density plot for income
sns.kdeplot(bank['income'], color='blue', linestyle='-')

# Density plot for debtinc
sns.kdeplot(bank['debtinc'], color='red', linestyle='-')

# Show the plot
plt.show()

Correlation coefficients - P.L correlation:

#from scipy.stats import pearsonr

# Calculate Pearson's correlation coefficient
correlation, _ = pearsonr(bank['income'], bank['debtinc'])

# Print the correlation coefficient
print("Pearson's correlation coefficient:", correlation)
## Pearson's correlation coefficient: -0.026777293424863746

Percentage of the explained variability:

#from scipy.stats import pearsonr

# Calculate Pearson's correlation coefficient
correlation, _ = pearsonr(bank['income'], bank['debtinc'])

# Calculate the percentage of explained variability
explained_variability = correlation**2 * 100

# Print the percentage of explained variability
print("Percentage of explained variability:", explained_variability)
## Percentage of explained variability: 0.07170234431612511

Partial and semipartial correlation:

#import pingouin as pg

# Ensure there are no missing values in the variables
complete_cases = bank.iloc[:, [bank.columns.get_loc('income'), bank.columns.get_loc('age'), bank.columns.get_loc('employ')]].dropna()

# Extract variables
income = complete_cases.iloc[:, complete_cases.columns.get_loc('income')]
age = complete_cases.iloc[:, complete_cases.columns.get_loc('age')]
tenure = complete_cases.iloc[:, complete_cases.columns.get_loc('employ')]

# Compute partial correlation
partial_corr = pg.partial_corr(data=complete_cases, x='income', y='age', covar='employ', method='pearson')

# Print the partial correlation coefficient
print("Partial correlation coefficient:", partial_corr['r'][0])
## <string>:3: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
## Partial correlation coefficient: 0.220812408872544

Difference between that one and the S-P coefficient:

# Ensure there are no missing values in the variables
complete_cases = bank[['income', 'age', 'employ']].dropna()

# Compute semi-partial (part) correlation
semi_partial_corr = pg.partial_corr(data=complete_cases, x='income', y='age', covar='employ', method='pearson')

# Print the semi-partial correlation coefficient
print("Semi-partial (part) correlation coefficient:", semi_partial_corr['r'].iloc[0])
## Semi-partial (part) correlation coefficient: 0.220812408872544

Rank correlation

# Create the boxplot
plt.figure(figsize=(10, 6))
## <Figure size 1000x600 with 0 Axes>
sns.boxplot(x='ed', y='income', data=bank, hue='ed', palette='light:#5A9', dodge=False)
## <Axes: xlabel='ed', ylabel='income'>
plt.legend([],[], frameon=False)
## <matplotlib.legend.Legend object at 0x0000020654B26D20>
# Customize the plot
plt.title('Income by Education Levels')
## Text(0.5, 1.0, 'Income by Education Levels')
plt.xlabel('Education Level')
## Text(0.5, 0, 'Education Level')
plt.ylabel('Income')
## Text(0, 0.5, 'Income')
# Show the plot
plt.show()

Kendal’s coefficient of rank correlation:

#(robust for ties)

#from scipy.stats import kendalltau

kendall_corr, _ = kendalltau(bank['income'], bank['debtinc'])

# Print the Kendall's coefficient of rank correlation
print("Kendall's coefficient of rank correlation:", kendall_corr)
## Kendall's coefficient of rank correlation: -0.012242089172559398

Point-biserial correlation

#I'm not sure in these codes. I hope it's true.

# Define risk_status based on debtinc thresholds
bank.loc[bank['debtinc'] <= 15, 'risk_status'] = 'Low'
## <string>:5: SettingWithCopyWarning: 
## A value is trying to be set on a copy of a slice from a DataFrame.
## Try using .loc[row_indexer,col_indexer] = value instead
## 
## See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
bank.loc[bank['debtinc'] > 15, 'risk_status'] = 'High'

# Create the boxplot
plt.figure(figsize=(10, 6))
## <Figure size 1000x600 with 0 Axes>
sns.boxplot(x='risk_status', y='income', data=bank, hue='risk_status', palette='light:#5A9', dodge=False)
## <Axes: xlabel='risk_status', ylabel='income'>
plt.legend([],[], frameon=False)
## <matplotlib.legend.Legend object at 0x00000206544BA0C0>
# Customize the plot
plt.title('Income by Risk Status')
## Text(0.5, 1.0, 'Income by Risk Status')
plt.xlabel('Risk Status')
## Text(0.5, 0, 'Risk Status')
plt.ylabel('Income')
## Text(0, 0.5, 'Income')
# Show the plot
plt.show()

Comparing QVar and 1 DVar

#from scipy.stats import pointbiserialr

point_biserial_corr, _ = pointbiserialr(bank['income'], bank['default'])

# Print the point-biserial correlation coefficient
print("Point-biserial correlation coefficient:", point_biserial_corr)
## Point-biserial correlation coefficient: -0.07096965661922319

Eta Coefficient:

# Calculating Pearson's linear correlation coefficient
pearson_corr, _ = pearsonr(bank['creddebt'], bank['othdebt'])

# Calculate Kendall's rank correlation coefficient
kendall_corr, _ = kendalltau(bank['creddebt'], bank['othdebt'])

# Calculate the eta coefficient
eta_coefficient = np.sqrt(1 - (kendall_corr**2 / pearson_corr**2))

# Print the eta coefficient
print("Eta coefficient:", eta_coefficient)
## Eta coefficient: 0.7083779812539175

Correlation matrix

# Ensuring all variables are numeric
numeric_columns = bank.select_dtypes(include=['number'])

# Calculate the correlation matrix for quantitative variables
correlation_matrix = numeric_columns.corr()

# Print the correlation matrix
print("Correlation Matrix:")
## Correlation Matrix:
print(correlation_matrix)
##                   age        ed    employ  ...  preddef3  log_income  log_debtinc
## age          1.000000  0.022325  0.536497  ...  0.013103    0.574346     0.020615
## ed           0.022325  1.000000 -0.153621  ...  0.007231    0.223493     0.009844
## employ       0.536497 -0.153621  1.000000  ... -0.025159    0.723908    -0.044799
## address      0.597591  0.056919  0.322334  ...  0.007748    0.368778     0.017506
## income       0.478710  0.235190  0.619681  ... -0.026165    0.893931    -0.023819
## debtinc      0.016398  0.008838 -0.031182  ...  0.989753   -0.014848     0.903224
## creddebt     0.295207  0.088274  0.403694  ...  0.496677    0.529251     0.442019
## othdebt      0.340217  0.165459  0.406091  ...  0.575755    0.601675     0.535911
## default     -0.137657  0.114676 -0.282978  ...  0.390154   -0.135226     0.340456
## preddef1    -0.314406  0.165519 -0.489568  ...  0.676668   -0.240918     0.587082
## preddef2    -0.228460  0.190319 -0.469639  ...  0.650348   -0.220286     0.559461
## preddef3     0.013103  0.007231 -0.025159  ...  1.000000   -0.012468     0.843109
## log_income   0.574346  0.223493  0.723908  ... -0.012468    1.000000    -0.020960
## log_debtinc  0.020615  0.009844 -0.044799  ...  0.843109   -0.020960     1.000000
## 
## [14 rows x 14 columns]
# Plotting the correlation matrix
plt.figure(figsize=(10, 8))
## <Figure size 1000x800 with 0 Axes>
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
## <Axes: >
plt.title('Correlation Matrix')
## Text(0.5, 1.0, 'Correlation Matrix')
plt.show()

Correlation matrix with scatterplots

# Step 1: Calculating the correlation matrix by group
correlation_matrix = bank_defaults.groupby('default').corr()

# Step 2: Plot the distribution of each variable by group
fig, axes = plt.subplots(nrows=len(bank_defaults.columns) - 1, ncols=1, figsize=(8, 6))
for i, col in enumerate(bank_defaults.columns[:-1]):
    sns.histplot(data=bank_defaults, x=col, hue='default', ax=axes[i], kde=True)
    axes[i].set_title(f'Distribution of {col} by default status')
## <Axes: xlabel='age', ylabel='Count'>
## Text(0.5, 1.0, 'Distribution of age by default status')
## <Axes: xlabel='ed', ylabel='Count'>
## Text(0.5, 1.0, 'Distribution of ed by default status')
## <Axes: xlabel='employ', ylabel='Count'>
## Text(0.5, 1.0, 'Distribution of employ by default status')
## <Axes: xlabel='address', ylabel='Count'>
## Text(0.5, 1.0, 'Distribution of address by default status')
## <Axes: xlabel='income', ylabel='Count'>
## Text(0.5, 1.0, 'Distribution of income by default status')
## <Axes: xlabel='debtinc', ylabel='Count'>
## Text(0.5, 1.0, 'Distribution of debtinc by default status')
## <Axes: xlabel='creddebt', ylabel='Count'>
## Text(0.5, 1.0, 'Distribution of creddebt by default status')
## <Axes: xlabel='othdebt', ylabel='Count'>
## Text(0.5, 1.0, 'Distribution of othdebt by default status')
## <Axes: xlabel='default', ylabel='Count'>
## Text(0.5, 1.0, 'Distribution of default by default status')
## <Axes: xlabel='preddef1', ylabel='Count'>
## Text(0.5, 1.0, 'Distribution of preddef1 by default status')
## <Axes: xlabel='preddef2', ylabel='Count'>
## Text(0.5, 1.0, 'Distribution of preddef2 by default status')
plt.tight_layout()
## <string>:2: UserWarning: Tight layout not applied. tight_layout cannot make Axes height small enough to accommodate all Axes decorations.
plt.show()

# Step 3: Displaying scatter plots with trend by group
sns.set(style="ticks")
sns.pairplot(data=bank_defaults, hue='default', diag_kind='kde')
## <seaborn.axisgrid.PairGrid object at 0x0000020651BE7E90>
plt.show()

Qualitative data

#from scipy.stats import chi2_contingency


# Creating a contingency table
contingency_table = pd.crosstab(bank_defaults['ed'], bank_defaults['default'])

# Perform chi-square test of independence
chi2, p, dof, expected = chi2_contingency(contingency_table)

# Print the results
print("Chi-square statistic:", chi2)
## Chi-square statistic: 11.492341703403657
print("P-value:", p)
## P-value: 0.02155395852574417
print("Degrees of freedom:", dof)
## Degrees of freedom: 4
print("Expected frequencies table:")
## Expected frequencies table:
print(expected)
## [[274.74857143  97.25142857]
##  [146.23714286  51.76285714]
##  [ 64.25571429  22.74428571]
##  [ 28.06571429   9.93428571]
##  [  3.69285714   1.30714286]]

Exercise 1

# Creating the contingency table
data = np.array([[435, 375], [147, 134]])
dane = pd.DataFrame(data, index=['Female', 'Male'], columns=['Yes', 'No'])

# Displaying the contingency table
print("Contingency Table:")
## Contingency Table:
print(dane)
##         Yes   No
## Female  435  375
## Male    147  134
# Chi-square test of independence
chi2_stat, p_val, dof, ex = chi2_contingency(dane)

print("\nChi-Square Test:")
## 
## Chi-Square Test:
print(f"Chi2 Stat: {chi2_stat}")
## Chi2 Stat: 0.1110272160868229
print(f"P-value: {p_val}")
## P-value: 0.7389776820172238
print(f"Degrees of Freedom: {dof}")
## Degrees of Freedom: 1
print("Expected Frequencies:")
## Expected Frequencies:
print(ex)
## [[432.09899175 377.90100825]
##  [149.90100825 131.09899175]]
# Phi Coefficient
n = np.sum(data)
phi = np.sqrt(chi2_stat / n)
print(f"\nPhi Coefficient: {phi}")
## 
## Phi Coefficient: 0.010087936733575699
fig, ax = plt.subplots(figsize=(8,6))
mosaic(dane.stack(), title='Mosaic Plot of Belief in Afterlife by Gender', ax=ax)
## (<Figure size 800x600 with 3 Axes>, {('Female', 'Yes'): (0.0, 0.0, 0.7387444081152442, 0.5352528608342562), ('Female', 'No'): (0.0, 0.5385751199704688, 0.7387444081152442, 0.4614248800295311), ('Male', 'Yes'): (0.7437195324933537, 0.0, 0.2562804675066464, 0.5213936936191342), ('Male', 'No'): (0.7437195324933537, 0.5247159527553469, 0.2562804675066464, 0.47528404724465306)})
plt.show()

# Bar plot
dane.plot(kind='bar', stacked=True)
## <Axes: >
plt.title('Belief in Afterlife by Gender')
## Text(0.5, 1.0, 'Belief in Afterlife by Gender')
plt.xlabel('Gender')
## Text(0.5, 0, 'Gender')
plt.ylabel('Count')
## Text(0, 0.5, 'Count')
plt.show()

# Proportion table
prop_table = dane / dane.sum().sum()
print("\nProportion Table:")
## 
## Proportion Table:
print(prop_table)
##              Yes        No
## Female  0.398717  0.343721
## Male    0.134739  0.122823

##Exercise 2


# Downloading the CSV file
url = "https://github.com/kflisikowski/ds/blob/master/titanic.csv?raw=true"
titanic = pd.read_csv(url, sep=";", index_col=0)

#Dropping rows:

titanic.dropna(inplace=True)

# Convert columns to factors
titanic['Status'] = pd.Categorical(titanic['Status'])
titanic['Gender'] = pd.Categorical(titanic['Gender'])

#Creating Contingency Table

contingency_table = pd.crosstab(titanic['Gender'], titanic['Status'])

#Performing Chi-Square Test

chi2, p, dof, expected = chi2_contingency(contingency_table)

print("Chi-Square Test:")
## Chi-Square Test:
print("Chi2 Statistic:", chi2)
## Chi2 Statistic: 60.862575878954544
print("P-value:", p)
## P-value: 6.120129207752533e-15
print("Degrees of Freedom:", dof)
## Degrees of Freedom: 1

#Calculating Phi coefficient

n = contingency_table.sum().sum()
phi = np.sqrt(chi2 / n)

print("\nPhi Coefficient:", phi)
## 
## Phi Coefficient: 0.3264803973833301

#Calculating Cramer’s Value:

min_dim = min(contingency_table.shape) - 1
v = np.sqrt(chi2 / (n * min_dim))

print("Cramer's V:", v)
## Cramer's V: 0.3264803973833301

#Ploting mosaic plot:

plt.figure(figsize=(8, 6))
## <Figure size 800x600 with 0 Axes>
mosaic_plot = contingency_table.plot(kind='bar', stacked=True)
plt.title('Mosaic Plot')
## Text(0.5, 1.0, 'Mosaic Plot')
plt.xlabel('Gender')
## Text(0.5, 0, 'Gender')
plt.ylabel('Count')
## Text(0, 0.5, 'Count')
plt.show()

#Ploting bar plot:

plt.figure(figsize=(8, 6))
## <Figure size 800x600 with 0 Axes>
bar_plot = contingency_table.plot(kind='bar')
plt.title('Bar Plot')
## Text(0.5, 1.0, 'Bar Plot')
plt.xlabel('Gender')
## Text(0.5, 0, 'Gender')
plt.ylabel('Count')
## Text(0, 0.5, 'Count')
plt.show()

LS0tDQp0aXRsZTogIkRlc2NyaXB0aXZlIFN0YXRpc3RpY3MiDQpzdWJ0aXRsZTogJ0JpdmFyaWF0ZSBBbmFseXNpcyAtIFJlcG9ydF80OiBQeXRob24nDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQphdXRob3I6ICJNYXJpdXN6IEdvZGxld3NraSwgSWduYWN5IEhpcnN6LCBIYXNhbiBCYXLEscWfIEfDtmsiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRoZW1lOiBjZXJ1bGVhbg0KICAgIGhpZ2hsaWdodDogdGV4dG1hdGUNCiAgICBmb250c2l6ZTogMTBwdA0KICAgIHRvYzogeWVzDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiBubw0KICAgIGRmX3ByaW50OiBkZWZhdWx0DQogICAgdG9jX2RlcHRoOiA1DQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpsaWJyYXJ5KHJldGljdWxhdGUpDQp1c2VfcHl0aG9uKCJDOi9Vc2Vycy9YWVovQXBwRGF0YS9Mb2NhbC9Qcm9ncmFtcy9QeXRob24vUHl0aG9uMzEyL3B5dGhvbi5leGUiKSAjWW91ciBweXRob24gcGF0aA0KYGBgDQoNCiMjIExpYnJhcmllcw0KYGBge3B5dGhvbn0NCmltcG9ydCByZXF1ZXN0cw0KaW1wb3J0IHB5cmVhZHN0YXQNCmltcG9ydCBwYW5kYXMgYXMgcGQNCmltcG9ydCBudW1weSBhcyBucA0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KZnJvbSBzY2lweSBpbXBvcnQgc3RhdHMNCmltcG9ydCBzZWFib3JuIGFzIHNucw0KZnJvbSBzY2lweS5zdGF0cyBpbXBvcnQgcGVhcnNvbnINCmltcG9ydCBwaW5nb3VpbiBhcyBwZw0KZnJvbSBzY2lweS5zdGF0cyBpbXBvcnQga2VuZGFsbHRhdQ0KZnJvbSBzY2lweS5zdGF0cyBpbXBvcnQgcG9pbnRiaXNlcmlhbHINCmZyb20gc2NpcHkuc3RhdHMgaW1wb3J0IGNoaTJfY29udGluZ2VuY3kNCmltcG9ydCBzdGF0c21vZGVscy5hcGkgYXMgc20NCmZyb20gc3RhdHNtb2RlbHMuZ3JhcGhpY3MubW9zYWljcGxvdCBpbXBvcnQgbW9zYWljDQpgYGANCg0KIyMgRG93bmxvYWRpbmcgdGhlIGZpbGUNCg0KYGBge3B5dGhvbn0NCg0KI2ltcG9ydCByZXF1ZXN0cw0KDQojIFVSTCBhbmQgZGVzdGluYXRpb24gZmlsZSBuYW1lIGZvciBkb3dubG9hZGluZyB0aGUgZGF0YQ0KdXJsID0gImh0dHBzOi8vZ2l0aHViLmNvbS9rZmxpc2lrb3dza2kvZHMvYmxvYi9tYXN0ZXIvYmFua19kZWZhdWx0cy5zYXY/cmF3PXRydWUiDQpkZXN0ZmlsZSA9ICJiYW5rX2RlZmF1bHRzLnNhdiINCg0KIyBEb3dubG9hZCB0aGUgZmlsZQ0KcmVzcG9uc2UgPSByZXF1ZXN0cy5nZXQodXJsKQ0KaWYgcmVzcG9uc2Uuc3RhdHVzX2NvZGUgPT0gMjAwOg0KICAgIHdpdGggb3BlbihkZXN0ZmlsZSwgJ3diJykgYXMgZmlsZToNCiAgICAgICAgZmlsZS53cml0ZShyZXNwb25zZS5jb250ZW50KQ0KICAgIHByaW50KCJGaWxlIGRvd25sb2FkZWQgc3VjY2Vzc2Z1bGx5LiIpDQplbHNlOg0KICAgIHByaW50KCJGYWlsZWQgdG8gZG93bmxvYWQgdGhlIGZpbGUuIFN0YXR1cyBjb2RlOiIsIHJlc3BvbnNlLnN0YXR1c19jb2RlKQ0KYGBgDQoNCiMjIFJlYWRpbmcgdGhlIC5zYXYgZmlsZSBvbiBQeXRob24NCg0KYGBge3B5dGhvbn0NCiMgaW1wb3J0IHB5cmVhZHN0YXQNCg0KYmFua19kZWZhdWx0cywgbWV0YSA9IHB5cmVhZHN0YXQucmVhZF9zYXYoZGVzdGZpbGUpDQpgYGANCg0KIyMgRHJvcHBpbmcgbWlzc2luZyB2YWx1ZXMNCg0KYGBge3B5dGhvbn0NCiNpbXBvcnQgcGFuZGFzIGFzIHBkDQoNCmJhbmsgPSBiYW5rX2RlZmF1bHRzLmRyb3BuYSgpDQoNCmBgYA0KDQojIyBDb252ZXJ0aW5nIGBkZWZhdWx0YCBjb2x1bW4gdG8gYSBjYXRlZ29yaWNhbCB0eXBlLg0KI0Fsc28gd2UncmUgY2hlY2tpbmcgdGhlIGRhdGEgd2l0aCAuaGVhZCgpICYgLmR0eXBlcyBmdW5jdGlvbnMgLyBpbnN0YW5jZXMgZnJvbSBQeXRob24uDQoNCmBgYHtweXRob259DQoNCiMgQ29udmVydCAnZGVmYXVsdCcgY29sdW1uIHRvIGEgY2F0ZWdvcmljYWwgdHlwZQ0KYmFua1snZGVmJ10gPSBiYW5rWydkZWZhdWx0J10uYXN0eXBlKCdjYXRlZ29yeScpDQoNCiMgUHJpbnQgdGhlIGZpcnN0IGZldyByb3dzIGFuZCBkYXRhIHR5cGVzIG9mIHRoZSBEYXRhRnJhbWUNCnByaW50KGJhbmsuaGVhZCgpKQ0KcHJpbnQoYmFuay5kdHlwZXMpDQpgYGANCg0KI1dlIGNhbiBzZWUgdGhlIHBsb3Q6DQpgYGB7cHl0aG9ufQ0KDQojIFNjYXR0ZXIgcGxvdCBvZiBvcmlnaW5hbCBkYXRhDQpwbHQuZmlndXJlKGZpZ3NpemU9KDcsIDYpKQ0KcGx0LnNjYXR0ZXIoYmFua1snaW5jb21lJ10sIGJhbmtbJ2RlYnRpbmMnXSwgbGFiZWw9J0RhdGEgUG9pbnRzJykNCg0KDQojIEFkZCB0aXRsZSBhbmQgYXhpcyBsYWJlbHMNCnBsdC50aXRsZSgiSW5jb21lIHZzLiBEZWJ0IEluY29tZSIpDQpwbHQueGxhYmVsKCJJbmNvbWUiKQ0KcGx0LnlsYWJlbCgiRGVidCBJbmNvbWUiKQ0KDQojIFNob3cgdGhlIHBsb3QNCnBsdC5zaG93KCkNCmBgYA0KDQojIyBBcHBseWluZyBsb2dhcml0aG1pYyB0cmFuc2Zvcm1hdGlvbjoNCg0KYGBge3B5dGhvbn0NCiNpbXBvcnQgbnVtcHkgYXMgbnANCg0KI0FwcGx5IGxvZ2FyaXRobWljIHRyYW5zZm9ybWF0aW9uDQpiYW5rLmxvY1s6LCAnbG9nX2luY29tZSddID0gbnAubG9nKGJhbmsubG9jWzosICdpbmNvbWUnXSkNCmJhbmsubG9jWzosICdsb2dfZGVidGluYyddID0gbnAubG9nKGJhbmsubG9jWzosICdkZWJ0aW5jJ10pDQoNCmBgYA0KDQojIyBOb3JtYWxpemluZyB0aGUgc2tld2VkIGRpc3RyaWJ1dGlvbiBvZiBpbmNvbWVzIHVzaW5nIGxvZzoNCg0KYGBge3B5dGhvbn0NCg0KIyBTY2F0dGVyIHBsb3Qgb2Ygb3JpZ2luYWwgZGF0YQ0KcGx0LmZpZ3VyZShmaWdzaXplPSg3LCA2KSkNCnBsdC5zY2F0dGVyKGJhbmtbJ2xvZ19pbmNvbWUnXSwgYmFua1snbG9nX2RlYnRpbmMnXSwgbGFiZWw9J0RhdGEgUG9pbnRzJykNCg0KDQojIEFkZCB0aXRsZSBhbmQgYXhpcyBsYWJlbHMNCnBsdC50aXRsZSgiTG9nIEluY29tZSB2cy4gTG9nIERlYnQgSW5jb21lIikNCnBsdC54bGFiZWwoIkxvZyBJbmNvbWUiKQ0KcGx0LnlsYWJlbCgiTG9nIERlYnQgSW5jb21lIikNCg0KIyBTaG93IHRoZSBwbG90DQpwbHQuc2hvdygpDQoNCg0KYGBgDQoNCiMjIENyZWF0aW5nIGEgc2NhdHRlciBwbG90IHdpdGggdGhlIGxvZ2FyaXRobWljYWxseSB0cmFuc2Zvcm1lZCBkYXRhDQoNCmBgYHtweXRob259DQoNCiMgU2NhdHRlciBwbG90IG9mIGxvZ2FyaXRobWljYWxseSB0cmFuc2Zvcm1lZCBkYXRhDQpwbHQuZmlndXJlKGZpZ3NpemU9KDcsIDYpKQ0KcGx0LnNjYXR0ZXIoYmFua1snbG9nX2luY29tZSddLCBiYW5rWydsb2dfZGVidGluYyddLCBsYWJlbD0nRGF0YSBQb2ludHMnKQ0KDQojIFBlcmZvcm0gbGluZWFyIHJlZ3Jlc3Npb24gb24gbG9nYXJpdGhtaWNhbGx5IHRyYW5zZm9ybWVkIGRhdGENCnNsb3BlX2xvZywgaW50ZXJjZXB0X2xvZywgcl92YWx1ZV9sb2csIHBfdmFsdWVfbG9nLCBzdGRfZXJyX2xvZyA9IHN0YXRzLmxpbnJlZ3Jlc3MoYmFua1snbG9nX2luY29tZSddLCBiYW5rWydsb2dfZGVidGluYyddKQ0KDQojIEdlbmVyYXRlIHZhbHVlcyBmb3IgdGhlIHJlZ3Jlc3Npb24gbGluZQ0KeF9sb2cgPSBucC5saW5zcGFjZShiYW5rWydsb2dfaW5jb21lJ10ubWluKCksIGJhbmtbJ2xvZ19pbmNvbWUnXS5tYXgoKSwgMTAwKQ0KeV9sb2cgPSBzbG9wZV9sb2cgKiB4X2xvZyArIGludGVyY2VwdF9sb2cNCg0KIyBQbG90IHRoZSByZWdyZXNzaW9uIGxpbmUNCnBsdC5wbG90KHhfbG9nLCB5X2xvZywgY29sb3I9J3JlZCcsIGxhYmVsPSdSZWdyZXNzaW9uIExpbmUnKQ0KDQojIEFkZCB0aXRsZSBhbmQgYXhpcyBsYWJlbHMNCnBsdC50aXRsZSgiTG9nYXJpdGhtaWMgSW5jb21lIHZzLiBMb2dhcml0aG1pYyBEZWJ0IEluY29tZSB3aXRoIFJlZ3Jlc3Npb24gTGluZSIpDQpwbHQueGxhYmVsKCJMb2dhcml0aG1pYyBJbmNvbWUiKQ0KcGx0LnlsYWJlbCgiTG9nYXJpdGhtaWMgRGVidCBJbmNvbWUiKQ0KDQojIEFkZCBhIGxlZ2VuZA0KcGx0LmxlZ2VuZCgpDQoNCiMgU2hvdyB0aGUgcGxvdA0KcGx0LnNob3coKQ0KDQpgYGANCg0KIyMgU2NhdHRlcnBsb3RzIGJ5IGdyb3Vwcw0KDQpgYGB7cHl0aG9ufQ0KDQojIEdyb3VwaW5nIHRoZSBkYXRhIGJ5IGVkdWNhdGlvbiBsZXZlbA0KZ3JvdXBlZF9kYXRhID0gYmFuay5ncm91cGJ5KCdlZCcpDQoNCiMgQ3JlYXRpbmcgc2VwYXJhdGUgc2NhdHRlcnBsb3RzIGZvciBlYWNoIGVkdWNhdGlvbiBsZXZlbA0KZm9yIG5hbWUsIGdyb3VwIGluIGdyb3VwZWRfZGF0YToNCiAgICBwbHQuZmlndXJlKGZpZ3NpemU9KDcsIDYpKQ0KICAgIHBsdC5zY2F0dGVyKGdyb3VwWydsb2dfaW5jb21lJ10sIGdyb3VwWydsb2dfZGVidGluYyddLCBsYWJlbD0nRGF0YSBQb2ludHMnKQ0KICAgIA0KICAgICMgUGVyZm9ybSBsaW5lYXIgcmVncmVzc2lvbg0KICAgIHNsb3BlLCBpbnRlcmNlcHQsIHJfdmFsdWUsIHBfdmFsdWUsIHN0ZF9lcnIgPSBzdGF0cy5saW5yZWdyZXNzKGdyb3VwWydsb2dfaW5jb21lJ10sIGdyb3VwWydsb2dfZGVidGluYyddKQ0KICAgIA0KICAgICMgR2VuZXJhdGUgdmFsdWVzIGZvciB0aGUgcmVncmVzc2lvbiBsaW5lDQogICAgeCA9IG5wLmxpbnNwYWNlKGdyb3VwWydsb2dfaW5jb21lJ10ubWluKCksIGdyb3VwWydsb2dfaW5jb21lJ10ubWF4KCksIDEwMCkNCiAgICB5ID0gc2xvcGUgKiB4ICsgaW50ZXJjZXB0DQogICAgDQogICAgIyBQbG90IHRoZSByZWdyZXNzaW9uIGxpbmUNCiAgICBwbHQucGxvdCh4LCB5LCBjb2xvcj0ncmVkJywgbGFiZWw9J1JlZ3Jlc3Npb24gTGluZScpDQogICAgDQogICAgIyBBZGQgdGl0bGUgYW5kIGF4aXMgbGFiZWxzDQogICAgcGx0LnRpdGxlKGYiTG9nYXJpdGhtaWMgSW5jb21lIHZzLiBMb2dhcml0aG1pYyBEZWJ0IEluY29tZSAoRWR1Y2F0aW9uIExldmVsOiB7bmFtZX0pIikNCiAgICBwbHQueGxhYmVsKCJMb2dhcml0aG1pYyBJbmNvbWUiKQ0KICAgIHBsdC55bGFiZWwoIkxvZ2FyaXRobWljIERlYnQgSW5jb21lIikNCiAgICANCiAgICAjIEFkZCBhIGxlZ2VuZA0KICAgIHBsdC5sZWdlbmQoKQ0KICAgIA0KICAgICMgU2hvdyB0aGUgcGxvdA0KICAgIHBsdC5zaG93KCkNCg0KYGBgDQoNCiMjIEVzdGltYXRlZCBEZW5zaXR5IFBsb3Rlcw0KI1RvIHNlZSBtb3JlIGNsb3NlbHkgaWYgdGhlcmUgYW55IGRpZmZlcmVuY2VzIGJldHdlZW4gdGhvc2UgdHdvIGRpc3RyaWJ1dGlvbnMgYWRkaW5nIHRoZWlyIGVzdGltYXRlZCBkZW5zaXR5IHBsb3RzDQoNCmBgYHtweXRob259DQoNCiMgU2NhdHRlciBwbG90IHdpdGggY29sb3IgYnkgZWR1Y2F0aW9uIGxldmVsDQpwbHQuZmlndXJlKGZpZ3NpemU9KDEwLCA2KSkNCnNucy5zY2F0dGVycGxvdCh4PWJhbmtbJ2luY29tZSddLCB5PWJhbmtbJ2RlYnRpbmMnXSwgaHVlPWJhbmtbJ2VkJ10sIHBhbGV0dGU9J3ZpcmlkaXMnKQ0KDQojIEFkZCB0aXRsZSBhbmQgYXhpcyBsYWJlbHMNCnBsdC50aXRsZSgiSW5jb21lIHZzLiBEZWJ0IEluY29tZSIpDQpwbHQueGxhYmVsKCJJbmNvbWUiKQ0KcGx0LnlsYWJlbCgiRGVidCBJbmNvbWUiKQ0KDQojIFNob3cgdGhlIHBsb3QNCnBsdC5zaG93KCkNCg0KIyBNYXJnaW5hbCBkZW5zaXR5IHBsb3Qgb2YgYWdlDQpwbHQuZmlndXJlKGZpZ3NpemU9KDgsIDYpKQ0Kc25zLmhpc3RwbG90KGJhbmtbJ2FnZSddLCBrZGU9VHJ1ZSkNCg0KIyBBZGQgdGl0bGUgYW5kIGF4aXMgbGFiZWxzDQpwbHQudGl0bGUoIkRlbnNpdHkgUGxvdCBvZiBBZ2UiKQ0KcGx0LnhsYWJlbCgiQWdlIikNCnBsdC55bGFiZWwoIkRlbnNpdHkiKQ0KDQojIFNob3cgdGhlIHBsb3QNCnBsdC5zaG93KCkNCg0KIyBNYXJnaW5hbCBkZW5zaXR5IHBsb3Qgb2YgeQ0KcGx0LmZpZ3VyZShmaWdzaXplPSg4LCA2KSkNCnNucy5oaXN0cGxvdChiYW5rX2RlZmF1bHRzWydhZ2UnXSwga2RlPVRydWUpDQoNCiMgQWRkIHRpdGxlIGFuZCBheGlzIGxhYmVscw0KcGx0LnRpdGxlKCJEZW5zaXR5IFBsb3Qgb2YgWSIpDQpwbHQueGxhYmVsKCJZIikNCnBsdC55bGFiZWwoIkRlbnNpdHkiKQ0KDQojIFNob3cgdGhlIHBsb3QNCnBsdC5zaG93KCkNCg0KDQpgYGANCg0KIyMgUGxvdHMgVG9nZXRoZXI6DQoNCg0KYGBge3B5dGhvbn0NCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmltcG9ydCBzZWFib3JuIGFzIHNucw0KDQojIFNldCB1cCB0aGUgbGF5b3V0DQpwbHQuZmlndXJlKGZpZ3NpemU9KDEyLCAxMCkpICAjIEFkanVzdCB0aGUgZmlndXJlIHNpemUgYXMgbmVlZGVkDQoNCiMgU2NhdHRlciBwbG90DQpwbHQuc3VicGxvdCgyLCAyLCAxKQ0Kc25zLnNjYXR0ZXJwbG90KHg9YmFua1snaW5jb21lJ10sIHk9YmFua1snZGVidGluYyddLCBodWU9YmFua1snZWQnXSwgcGFsZXR0ZT0ndmlyaWRpcycpDQpwbHQudGl0bGUoIkluY29tZSB2cy4gRGVidCBJbmNvbWUiKQ0KcGx0LnhsYWJlbCgiSW5jb21lIikNCnBsdC55bGFiZWwoIkRlYnQgSW5jb21lIikNCg0KDQoNCiMgTWFyZ2luYWwgZGVuc2l0eSBwbG90IG9mIGFnZSAodG9wIHBhbmVsKQ0KcGx0LnN1YnBsb3QoMiwgMiwgMikNCnNucy5oaXN0cGxvdChiYW5rWydhZ2UnXSwga2RlPVRydWUpDQpwbHQudGl0bGUoIkRlbnNpdHkgUGxvdCBvZiBBZ2UiKQ0KcGx0LnhsYWJlbCgiQWdlIikNCnBsdC55bGFiZWwoIkRlbnNpdHkiKQ0KDQojIENyZWF0ZSBzcGFjZSBmb3IgdGhlIHRoaXJkIHBsb3QNCnBsdC5zdWJwbG90KDIsIDIsIDMpDQpwbHQuYXhpcygnb2ZmJykgICMgVHVybiBvZmYgdGhlIGF4aXMgZm9yIHRoaXMgc3VicGxvdA0KDQojIE1hcmdpbmFsIGRlbnNpdHkgcGxvdCBvZiB5IChyaWdodCBwYW5lbCkNCnBsdC5zdWJwbG90KDIsIDIsIDQpDQpzbnMuaGlzdHBsb3QoYmFua19kZWZhdWx0c1snYWdlJ10sIGtkZT1UcnVlKQ0KcGx0LnRpdGxlKCJEZW5zaXR5IFBsb3Qgb2YgWSIpDQpwbHQueGxhYmVsKCJZIikNCnBsdC55bGFiZWwoIkRlbnNpdHkiKQ0KDQojIEFkanVzdCBsYXlvdXQNCnBsdC50aWdodF9sYXlvdXQoKQ0KDQojIFNob3cgdGhlIHBsb3QNCnBsdC5zaG93KCkNCmBgYA0KDQoNCiMjR2l2aW5nIERlbnNpdHkgQ3VydmVzIHRvIFNjYXR0ZXJwbG90cw0KDQpgYGB7cHl0aG9ufQ0KIyBTY2F0dGVyIHBsb3QNCnBsdC5maWd1cmUoZmlnc2l6ZT0oMTAsIDYpKQ0KcGx0LnNjYXR0ZXIoYmFua1snaW5jb21lJ10sIGJhbmtbJ2RlYnRpbmMnXSwgYz1iYW5rWydlZCddLCBjbWFwPSd2aXJpZGlzJykNCnBsdC50aXRsZSgiSW5jb21lIHZzLiBEZWJ0IEluY29tZSIpDQpwbHQueGxhYmVsKCJJbmNvbWUiKQ0KcGx0LnlsYWJlbCgiRGVidCBJbmNvbWUiKQ0KDQojIERlbnNpdHkgcGxvdCBmb3IgaW5jb21lDQpzbnMua2RlcGxvdChiYW5rWydpbmNvbWUnXSwgY29sb3I9J2JsdWUnLCBsaW5lc3R5bGU9Jy0nKQ0KDQojIERlbnNpdHkgcGxvdCBmb3IgZGVidGluYw0Kc25zLmtkZXBsb3QoYmFua1snZGVidGluYyddLCBjb2xvcj0ncmVkJywgbGluZXN0eWxlPSctJykNCg0KIyBTaG93IHRoZSBwbG90DQpwbHQuc2hvdygpDQoNCmBgYA0KDQojIyBDb3JyZWxhdGlvbiBjb2VmZmljaWVudHMgLSBQLkwgY29ycmVsYXRpb246DQpgYGB7cHl0aG9ufQ0KDQojZnJvbSBzY2lweS5zdGF0cyBpbXBvcnQgcGVhcnNvbnINCg0KIyBDYWxjdWxhdGUgUGVhcnNvbidzIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50DQpjb3JyZWxhdGlvbiwgXyA9IHBlYXJzb25yKGJhbmtbJ2luY29tZSddLCBiYW5rWydkZWJ0aW5jJ10pDQoNCiMgUHJpbnQgdGhlIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50DQpwcmludCgiUGVhcnNvbidzIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50OiIsIGNvcnJlbGF0aW9uKQ0KYGBgDQoNCiMjIFBlcmNlbnRhZ2Ugb2YgdGhlIGV4cGxhaW5lZCB2YXJpYWJpbGl0eToNCmBgYHtweXRob259DQojZnJvbSBzY2lweS5zdGF0cyBpbXBvcnQgcGVhcnNvbnINCg0KIyBDYWxjdWxhdGUgUGVhcnNvbidzIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50DQpjb3JyZWxhdGlvbiwgXyA9IHBlYXJzb25yKGJhbmtbJ2luY29tZSddLCBiYW5rWydkZWJ0aW5jJ10pDQoNCiMgQ2FsY3VsYXRlIHRoZSBwZXJjZW50YWdlIG9mIGV4cGxhaW5lZCB2YXJpYWJpbGl0eQ0KZXhwbGFpbmVkX3ZhcmlhYmlsaXR5ID0gY29ycmVsYXRpb24qKjIgKiAxMDANCg0KIyBQcmludCB0aGUgcGVyY2VudGFnZSBvZiBleHBsYWluZWQgdmFyaWFiaWxpdHkNCnByaW50KCJQZXJjZW50YWdlIG9mIGV4cGxhaW5lZCB2YXJpYWJpbGl0eToiLCBleHBsYWluZWRfdmFyaWFiaWxpdHkpDQpgYGANCg0KIyMgUGFydGlhbCBhbmQgc2VtaXBhcnRpYWwgY29ycmVsYXRpb246IA0KYGBge3B5dGhvbn0NCiNpbXBvcnQgcGluZ291aW4gYXMgcGcNCg0KIyBFbnN1cmUgdGhlcmUgYXJlIG5vIG1pc3NpbmcgdmFsdWVzIGluIHRoZSB2YXJpYWJsZXMNCmNvbXBsZXRlX2Nhc2VzID0gYmFuay5pbG9jWzosIFtiYW5rLmNvbHVtbnMuZ2V0X2xvYygnaW5jb21lJyksIGJhbmsuY29sdW1ucy5nZXRfbG9jKCdhZ2UnKSwgYmFuay5jb2x1bW5zLmdldF9sb2MoJ2VtcGxveScpXV0uZHJvcG5hKCkNCg0KIyBFeHRyYWN0IHZhcmlhYmxlcw0KaW5jb21lID0gY29tcGxldGVfY2FzZXMuaWxvY1s6LCBjb21wbGV0ZV9jYXNlcy5jb2x1bW5zLmdldF9sb2MoJ2luY29tZScpXQ0KYWdlID0gY29tcGxldGVfY2FzZXMuaWxvY1s6LCBjb21wbGV0ZV9jYXNlcy5jb2x1bW5zLmdldF9sb2MoJ2FnZScpXQ0KdGVudXJlID0gY29tcGxldGVfY2FzZXMuaWxvY1s6LCBjb21wbGV0ZV9jYXNlcy5jb2x1bW5zLmdldF9sb2MoJ2VtcGxveScpXQ0KDQojIENvbXB1dGUgcGFydGlhbCBjb3JyZWxhdGlvbg0KcGFydGlhbF9jb3JyID0gcGcucGFydGlhbF9jb3JyKGRhdGE9Y29tcGxldGVfY2FzZXMsIHg9J2luY29tZScsIHk9J2FnZScsIGNvdmFyPSdlbXBsb3knLCBtZXRob2Q9J3BlYXJzb24nKQ0KDQojIFByaW50IHRoZSBwYXJ0aWFsIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50DQpwcmludCgiUGFydGlhbCBjb3JyZWxhdGlvbiBjb2VmZmljaWVudDoiLCBwYXJ0aWFsX2NvcnJbJ3InXVswXSkNCg0KYGBgDQoNCiMjIERpZmZlcmVuY2UgYmV0d2VlbiB0aGF0IG9uZSBhbmQgdGhlIFMtUCBjb2VmZmljaWVudDoNCmBgYHtweXRob259DQoNCiMgRW5zdXJlIHRoZXJlIGFyZSBubyBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgdmFyaWFibGVzDQpjb21wbGV0ZV9jYXNlcyA9IGJhbmtbWydpbmNvbWUnLCAnYWdlJywgJ2VtcGxveSddXS5kcm9wbmEoKQ0KDQojIENvbXB1dGUgc2VtaS1wYXJ0aWFsIChwYXJ0KSBjb3JyZWxhdGlvbg0Kc2VtaV9wYXJ0aWFsX2NvcnIgPSBwZy5wYXJ0aWFsX2NvcnIoZGF0YT1jb21wbGV0ZV9jYXNlcywgeD0naW5jb21lJywgeT0nYWdlJywgY292YXI9J2VtcGxveScsIG1ldGhvZD0ncGVhcnNvbicpDQoNCiMgUHJpbnQgdGhlIHNlbWktcGFydGlhbCBjb3JyZWxhdGlvbiBjb2VmZmljaWVudA0KcHJpbnQoIlNlbWktcGFydGlhbCAocGFydCkgY29ycmVsYXRpb24gY29lZmZpY2llbnQ6Iiwgc2VtaV9wYXJ0aWFsX2NvcnJbJ3InXS5pbG9jWzBdKQ0KDQpgYGANCg0KIyMgUmFuayBjb3JyZWxhdGlvbiANCmBgYHtweXRob259DQoNCiMgQ3JlYXRlIHRoZSBib3hwbG90DQpwbHQuZmlndXJlKGZpZ3NpemU9KDEwLCA2KSkNCnNucy5ib3hwbG90KHg9J2VkJywgeT0naW5jb21lJywgZGF0YT1iYW5rLCBodWU9J2VkJywgcGFsZXR0ZT0nbGlnaHQ6IzVBOScsIGRvZGdlPUZhbHNlKQ0KcGx0LmxlZ2VuZChbXSxbXSwgZnJhbWVvbj1GYWxzZSkNCg0KIyBDdXN0b21pemUgdGhlIHBsb3QNCnBsdC50aXRsZSgnSW5jb21lIGJ5IEVkdWNhdGlvbiBMZXZlbHMnKQ0KcGx0LnhsYWJlbCgnRWR1Y2F0aW9uIExldmVsJykNCnBsdC55bGFiZWwoJ0luY29tZScpDQoNCiMgU2hvdyB0aGUgcGxvdA0KcGx0LnNob3coKQ0KYGBgDQoNCiMjIEtlbmRhbCdzIGNvZWZmaWNpZW50IG9mIHJhbmsgY29ycmVsYXRpb246DQojKHJvYnVzdCBmb3IgdGllcykNCmBgYHtweXRob259DQojZnJvbSBzY2lweS5zdGF0cyBpbXBvcnQga2VuZGFsbHRhdQ0KDQprZW5kYWxsX2NvcnIsIF8gPSBrZW5kYWxsdGF1KGJhbmtbJ2luY29tZSddLCBiYW5rWydkZWJ0aW5jJ10pDQoNCiMgUHJpbnQgdGhlIEtlbmRhbGwncyBjb2VmZmljaWVudCBvZiByYW5rIGNvcnJlbGF0aW9uDQpwcmludCgiS2VuZGFsbCdzIGNvZWZmaWNpZW50IG9mIHJhbmsgY29ycmVsYXRpb246Iiwga2VuZGFsbF9jb3JyKQ0KDQpgYGAgDQoNCiMjIFBvaW50LWJpc2VyaWFsIGNvcnJlbGF0aW9uDQpgYGB7cHl0aG9ufQ0KDQojSSdtIG5vdCBzdXJlIGluIHRoZXNlIGNvZGVzLiBJIGhvcGUgaXQncyB0cnVlLg0KDQojIERlZmluZSByaXNrX3N0YXR1cyBiYXNlZCBvbiBkZWJ0aW5jIHRocmVzaG9sZHMNCmJhbmsubG9jW2JhbmtbJ2RlYnRpbmMnXSA8PSAxNSwgJ3Jpc2tfc3RhdHVzJ10gPSAnTG93Jw0KYmFuay5sb2NbYmFua1snZGVidGluYyddID4gMTUsICdyaXNrX3N0YXR1cyddID0gJ0hpZ2gnDQoNCiMgQ3JlYXRlIHRoZSBib3hwbG90DQpwbHQuZmlndXJlKGZpZ3NpemU9KDEwLCA2KSkNCnNucy5ib3hwbG90KHg9J3Jpc2tfc3RhdHVzJywgeT0naW5jb21lJywgZGF0YT1iYW5rLCBodWU9J3Jpc2tfc3RhdHVzJywgcGFsZXR0ZT0nbGlnaHQ6IzVBOScsIGRvZGdlPUZhbHNlKQ0KcGx0LmxlZ2VuZChbXSxbXSwgZnJhbWVvbj1GYWxzZSkNCg0KIyBDdXN0b21pemUgdGhlIHBsb3QNCnBsdC50aXRsZSgnSW5jb21lIGJ5IFJpc2sgU3RhdHVzJykNCnBsdC54bGFiZWwoJ1Jpc2sgU3RhdHVzJykNCnBsdC55bGFiZWwoJ0luY29tZScpDQoNCiMgU2hvdyB0aGUgcGxvdA0KcGx0LnNob3coKQ0KYGBgDQoNCiMjIENvbXBhcmluZyBRVmFyIGFuZCAxIERWYXINCmBgYHtweXRob259DQojZnJvbSBzY2lweS5zdGF0cyBpbXBvcnQgcG9pbnRiaXNlcmlhbHINCg0KcG9pbnRfYmlzZXJpYWxfY29yciwgXyA9IHBvaW50YmlzZXJpYWxyKGJhbmtbJ2luY29tZSddLCBiYW5rWydkZWZhdWx0J10pDQoNCiMgUHJpbnQgdGhlIHBvaW50LWJpc2VyaWFsIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50DQpwcmludCgiUG9pbnQtYmlzZXJpYWwgY29ycmVsYXRpb24gY29lZmZpY2llbnQ6IiwgcG9pbnRfYmlzZXJpYWxfY29ycikNCmBgYA0KDQojIyBFdGEgQ29lZmZpY2llbnQ6IA0KYGBge3B5dGhvbn0NCiMgQ2FsY3VsYXRpbmcgUGVhcnNvbidzIGxpbmVhciBjb3JyZWxhdGlvbiBjb2VmZmljaWVudA0KcGVhcnNvbl9jb3JyLCBfID0gcGVhcnNvbnIoYmFua1snY3JlZGRlYnQnXSwgYmFua1snb3RoZGVidCddKQ0KDQojIENhbGN1bGF0ZSBLZW5kYWxsJ3MgcmFuayBjb3JyZWxhdGlvbiBjb2VmZmljaWVudA0Ka2VuZGFsbF9jb3JyLCBfID0ga2VuZGFsbHRhdShiYW5rWydjcmVkZGVidCddLCBiYW5rWydvdGhkZWJ0J10pDQoNCiMgQ2FsY3VsYXRlIHRoZSBldGEgY29lZmZpY2llbnQNCmV0YV9jb2VmZmljaWVudCA9IG5wLnNxcnQoMSAtIChrZW5kYWxsX2NvcnIqKjIgLyBwZWFyc29uX2NvcnIqKjIpKQ0KDQojIFByaW50IHRoZSBldGEgY29lZmZpY2llbnQNCnByaW50KCJFdGEgY29lZmZpY2llbnQ6IiwgZXRhX2NvZWZmaWNpZW50KQ0KYGBgDQoNCiMjIENvcnJlbGF0aW9uIG1hdHJpeA0KYGBge3B5dGhvbn0NCg0KIyBFbnN1cmluZyBhbGwgdmFyaWFibGVzIGFyZSBudW1lcmljDQpudW1lcmljX2NvbHVtbnMgPSBiYW5rLnNlbGVjdF9kdHlwZXMoaW5jbHVkZT1bJ251bWJlciddKQ0KDQojIENhbGN1bGF0ZSB0aGUgY29ycmVsYXRpb24gbWF0cml4IGZvciBxdWFudGl0YXRpdmUgdmFyaWFibGVzDQpjb3JyZWxhdGlvbl9tYXRyaXggPSBudW1lcmljX2NvbHVtbnMuY29ycigpDQoNCiMgUHJpbnQgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeA0KcHJpbnQoIkNvcnJlbGF0aW9uIE1hdHJpeDoiKQ0KcHJpbnQoY29ycmVsYXRpb25fbWF0cml4KQ0KDQojIFBsb3R0aW5nIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXgNCnBsdC5maWd1cmUoZmlnc2l6ZT0oMTAsIDgpKQ0Kc25zLmhlYXRtYXAoY29ycmVsYXRpb25fbWF0cml4LCBhbm5vdD1UcnVlLCBjbWFwPSdjb29sd2FybScsIGZtdD0iLjJmIikNCnBsdC50aXRsZSgnQ29ycmVsYXRpb24gTWF0cml4JykNCnBsdC5zaG93KCkNCmBgYA0KDQojIyBDb3JyZWxhdGlvbiBtYXRyaXggd2l0aCBzY2F0dGVycGxvdHMgDQpgYGB7cHl0aG9ufQ0KIyBTdGVwIDE6IENhbGN1bGF0aW5nIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXggYnkgZ3JvdXANCmNvcnJlbGF0aW9uX21hdHJpeCA9IGJhbmtfZGVmYXVsdHMuZ3JvdXBieSgnZGVmYXVsdCcpLmNvcnIoKQ0KDQojIFN0ZXAgMjogUGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGVhY2ggdmFyaWFibGUgYnkgZ3JvdXANCmZpZywgYXhlcyA9IHBsdC5zdWJwbG90cyhucm93cz1sZW4oYmFua19kZWZhdWx0cy5jb2x1bW5zKSAtIDEsIG5jb2xzPTEsIGZpZ3NpemU9KDgsIDYpKQ0KZm9yIGksIGNvbCBpbiBlbnVtZXJhdGUoYmFua19kZWZhdWx0cy5jb2x1bW5zWzotMV0pOg0KICAgIHNucy5oaXN0cGxvdChkYXRhPWJhbmtfZGVmYXVsdHMsIHg9Y29sLCBodWU9J2RlZmF1bHQnLCBheD1heGVzW2ldLCBrZGU9VHJ1ZSkNCiAgICBheGVzW2ldLnNldF90aXRsZShmJ0Rpc3RyaWJ1dGlvbiBvZiB7Y29sfSBieSBkZWZhdWx0IHN0YXR1cycpDQoNCnBsdC50aWdodF9sYXlvdXQoKQ0KcGx0LnNob3coKQ0KDQojIFN0ZXAgMzogRGlzcGxheWluZyBzY2F0dGVyIHBsb3RzIHdpdGggdHJlbmQgYnkgZ3JvdXANCnNucy5zZXQoc3R5bGU9InRpY2tzIikNCnNucy5wYWlycGxvdChkYXRhPWJhbmtfZGVmYXVsdHMsIGh1ZT0nZGVmYXVsdCcsIGRpYWdfa2luZD0na2RlJykNCnBsdC5zaG93KCkNCmBgYA0KDQojIyBRdWFsaXRhdGl2ZSBkYXRhDQpgYGB7cHl0aG9ufQ0KI2Zyb20gc2NpcHkuc3RhdHMgaW1wb3J0IGNoaTJfY29udGluZ2VuY3kNCg0KDQojIENyZWF0aW5nIGEgY29udGluZ2VuY3kgdGFibGUNCmNvbnRpbmdlbmN5X3RhYmxlID0gcGQuY3Jvc3N0YWIoYmFua19kZWZhdWx0c1snZWQnXSwgYmFua19kZWZhdWx0c1snZGVmYXVsdCddKQ0KDQojIFBlcmZvcm0gY2hpLXNxdWFyZSB0ZXN0IG9mIGluZGVwZW5kZW5jZQ0KY2hpMiwgcCwgZG9mLCBleHBlY3RlZCA9IGNoaTJfY29udGluZ2VuY3koY29udGluZ2VuY3lfdGFibGUpDQoNCiMgUHJpbnQgdGhlIHJlc3VsdHMNCnByaW50KCJDaGktc3F1YXJlIHN0YXRpc3RpYzoiLCBjaGkyKQ0KcHJpbnQoIlAtdmFsdWU6IiwgcCkNCnByaW50KCJEZWdyZWVzIG9mIGZyZWVkb206IiwgZG9mKQ0KcHJpbnQoIkV4cGVjdGVkIGZyZXF1ZW5jaWVzIHRhYmxlOiIpDQpwcmludChleHBlY3RlZCkNCmBgYA0KDQojIyBFeGVyY2lzZSAxDQpgYGB7cHl0aG9ufQ0KDQojIENyZWF0aW5nIHRoZSBjb250aW5nZW5jeSB0YWJsZQ0KZGF0YSA9IG5wLmFycmF5KFtbNDM1LCAzNzVdLCBbMTQ3LCAxMzRdXSkNCmRhbmUgPSBwZC5EYXRhRnJhbWUoZGF0YSwgaW5kZXg9WydGZW1hbGUnLCAnTWFsZSddLCBjb2x1bW5zPVsnWWVzJywgJ05vJ10pDQoNCiMgRGlzcGxheWluZyB0aGUgY29udGluZ2VuY3kgdGFibGUNCnByaW50KCJDb250aW5nZW5jeSBUYWJsZToiKQ0KcHJpbnQoZGFuZSkNCg0KIyBDaGktc3F1YXJlIHRlc3Qgb2YgaW5kZXBlbmRlbmNlDQpjaGkyX3N0YXQsIHBfdmFsLCBkb2YsIGV4ID0gY2hpMl9jb250aW5nZW5jeShkYW5lKQ0KDQpwcmludCgiXG5DaGktU3F1YXJlIFRlc3Q6IikNCnByaW50KGYiQ2hpMiBTdGF0OiB7Y2hpMl9zdGF0fSIpDQpwcmludChmIlAtdmFsdWU6IHtwX3ZhbH0iKQ0KcHJpbnQoZiJEZWdyZWVzIG9mIEZyZWVkb206IHtkb2Z9IikNCnByaW50KCJFeHBlY3RlZCBGcmVxdWVuY2llczoiKQ0KcHJpbnQoZXgpDQoNCiMgUGhpIENvZWZmaWNpZW50DQpuID0gbnAuc3VtKGRhdGEpDQpwaGkgPSBucC5zcXJ0KGNoaTJfc3RhdCAvIG4pDQpwcmludChmIlxuUGhpIENvZWZmaWNpZW50OiB7cGhpfSIpDQoNCmZpZywgYXggPSBwbHQuc3VicGxvdHMoZmlnc2l6ZT0oOCw2KSkNCm1vc2FpYyhkYW5lLnN0YWNrKCksIHRpdGxlPSdNb3NhaWMgUGxvdCBvZiBCZWxpZWYgaW4gQWZ0ZXJsaWZlIGJ5IEdlbmRlcicsIGF4PWF4KQ0KcGx0LnNob3coKQ0KDQojIEJhciBwbG90DQpkYW5lLnBsb3Qoa2luZD0nYmFyJywgc3RhY2tlZD1UcnVlKQ0KcGx0LnRpdGxlKCdCZWxpZWYgaW4gQWZ0ZXJsaWZlIGJ5IEdlbmRlcicpDQpwbHQueGxhYmVsKCdHZW5kZXInKQ0KcGx0LnlsYWJlbCgnQ291bnQnKQ0KcGx0LnNob3coKQ0KDQojIFByb3BvcnRpb24gdGFibGUNCnByb3BfdGFibGUgPSBkYW5lIC8gZGFuZS5zdW0oKS5zdW0oKQ0KcHJpbnQoIlxuUHJvcG9ydGlvbiBUYWJsZToiKQ0KcHJpbnQocHJvcF90YWJsZSkNCmBgYA0KDQojI0V4ZXJjaXNlIDINCmBgYHtweXRob259DQoNCiMgRG93bmxvYWRpbmcgdGhlIENTViBmaWxlDQp1cmwgPSAiaHR0cHM6Ly9naXRodWIuY29tL2tmbGlzaWtvd3NraS9kcy9ibG9iL21hc3Rlci90aXRhbmljLmNzdj9yYXc9dHJ1ZSINCnRpdGFuaWMgPSBwZC5yZWFkX2Nzdih1cmwsIHNlcD0iOyIsIGluZGV4X2NvbD0wKQ0KYGBgDQoNCiNEcm9wcGluZyByb3dzOg0KYGBge3B5dGhvbn0NCnRpdGFuaWMuZHJvcG5hKGlucGxhY2U9VHJ1ZSkNCg0KIyBDb252ZXJ0IGNvbHVtbnMgdG8gZmFjdG9ycw0KdGl0YW5pY1snU3RhdHVzJ10gPSBwZC5DYXRlZ29yaWNhbCh0aXRhbmljWydTdGF0dXMnXSkNCnRpdGFuaWNbJ0dlbmRlciddID0gcGQuQ2F0ZWdvcmljYWwodGl0YW5pY1snR2VuZGVyJ10pDQpgYGANCg0KI0NyZWF0aW5nIENvbnRpbmdlbmN5IFRhYmxlDQpgYGB7cHl0aG9ufQ0KY29udGluZ2VuY3lfdGFibGUgPSBwZC5jcm9zc3RhYih0aXRhbmljWydHZW5kZXInXSwgdGl0YW5pY1snU3RhdHVzJ10pDQpgYGANCiNQZXJmb3JtaW5nIENoaS1TcXVhcmUgVGVzdA0KYGBge3B5dGhvbn0NCmNoaTIsIHAsIGRvZiwgZXhwZWN0ZWQgPSBjaGkyX2NvbnRpbmdlbmN5KGNvbnRpbmdlbmN5X3RhYmxlKQ0KDQpwcmludCgiQ2hpLVNxdWFyZSBUZXN0OiIpDQpwcmludCgiQ2hpMiBTdGF0aXN0aWM6IiwgY2hpMikNCnByaW50KCJQLXZhbHVlOiIsIHApDQpwcmludCgiRGVncmVlcyBvZiBGcmVlZG9tOiIsIGRvZikNCmBgYA0KDQojQ2FsY3VsYXRpbmcgUGhpIGNvZWZmaWNpZW50DQpgYGB7cHl0aG9ufQ0KbiA9IGNvbnRpbmdlbmN5X3RhYmxlLnN1bSgpLnN1bSgpDQpwaGkgPSBucC5zcXJ0KGNoaTIgLyBuKQ0KDQpwcmludCgiXG5QaGkgQ29lZmZpY2llbnQ6IiwgcGhpKQ0KYGBgDQoNCiNDYWxjdWxhdGluZyBDcmFtZXIncyBWYWx1ZToNCmBgYHtweXRob259DQptaW5fZGltID0gbWluKGNvbnRpbmdlbmN5X3RhYmxlLnNoYXBlKSAtIDENCnYgPSBucC5zcXJ0KGNoaTIgLyAobiAqIG1pbl9kaW0pKQ0KDQpwcmludCgiQ3JhbWVyJ3MgVjoiLCB2KQ0KYGBgDQoNCiNQbG90aW5nIG1vc2FpYyBwbG90Og0KYGBge3B5dGhvbn0NCnBsdC5maWd1cmUoZmlnc2l6ZT0oOCwgNikpDQptb3NhaWNfcGxvdCA9IGNvbnRpbmdlbmN5X3RhYmxlLnBsb3Qoa2luZD0nYmFyJywgc3RhY2tlZD1UcnVlKQ0KcGx0LnRpdGxlKCdNb3NhaWMgUGxvdCcpDQpwbHQueGxhYmVsKCdHZW5kZXInKQ0KcGx0LnlsYWJlbCgnQ291bnQnKQ0KcGx0LnNob3coKQ0KYGBgDQoNCiNQbG90aW5nIGJhciBwbG90Og0KYGBge3B5dGhvbn0NCnBsdC5maWd1cmUoZmlnc2l6ZT0oOCwgNikpDQpiYXJfcGxvdCA9IGNvbnRpbmdlbmN5X3RhYmxlLnBsb3Qoa2luZD0nYmFyJykNCnBsdC50aXRsZSgnQmFyIFBsb3QnKQ0KcGx0LnhsYWJlbCgnR2VuZGVyJykNCnBsdC55bGFiZWwoJ0NvdW50JykNCnBsdC5zaG93KCkNCmBgYA==