Class 1 transcript summarized and broken down into sections going into case studfy 1.

  1. Course Introduction and Structure
    • Instructor: Professor Slater.
    • Course: “Quantifying the World.”
    • Syllabus and Textbook:
      • Free online textbook available.
    • Key Components of Grading:
      • Case studies (50%): Seven biweekly case studies (odd weeks). Final case study counts double.
      • Participation (30%): Camera on during class, and pre-session submissions.
      • Quizzes (20%): Weekly, due Tuesday midnight, auto-graded.
    • Late Submission Policy:
      • Case studies: 10% deduction per day.
      • Pre-session and quizzes: No late submissions accepted.
  2. Case Study Requirements
    • Format:
      • Technical write-up aimed at explaining data science analysis.
      • Include all steps, such as data preparation, model selection, and results.
    • Key Elements:
      • Data cleaning (e.g., imputation, feature handling).
      • Model choice and configuration.
      • Metrics like loss function, accuracy, precision, recall, etc.
      • Visualizations (residual plots, confusion matrix, etc.).
    • Mathematical Representations:
      • Cross-validation: Split data into training and test sets, rotating test sets for unbiased evaluation.
      • Regularization:
        • L1 (Lasso): \(\text{Loss} = ||y - X\beta||^2_2 + \lambda ||\beta||_1\)
        • L2 (Ridge): \(\text{Loss} = ||y - X\beta||^2_2 + \lambda ||\beta||^2_2\)
  3. Python-Specific Concepts
    • Python’s Strengths:
      • Versatility across data science and general programming tasks.
      • Key libraries: NumPy, Pandas, PyTorch.
    • Python Indexing:
      • Zero-based indexing: list[0] refers to the first element.
      • Slicing: list[start:end] includes start but excludes end.
    • Environments:
      • Importance of using virtual environments to manage Python dependencies.
  4. Mathematical Principles for Analysis
    • Metrics:
      • Mean Absolute Error (MAE): \(MAE = \frac{1}{n} \sum_{i=1}^n |y_i - \hat{y}_i|\)
      • Precision, Recall, Sensitivity, Specificity.
    • Data Handling:
      • Feature selection using L1 regularization.
      • Handling highly correlated features: Avoid dropping unless 100% correlated or uninformative.
  5. Case Study Example: Superconductors
    • Dataset:
      • Contains chemical compositions and their critical temperatures.
      • Tasks:
        • Merge training and unique datasets.
        • Handle duplicate target columns.
        • Train models using L1 and L2 regularization.
    • Visualizations:
      • Include clear, legible graphs like feature importance and residual plots.
  6. Key Takeaways and Recommendations
    • Avoid subjective terms like “performed well”—use precise metrics.
    • Always cross-validate models unless computationally prohibitive.
    • Submit all relevant code for transparency, even if unorganized.

To expand on the case study and demonstrate the concepts using the provided data (train.csv and unique_m.csv), I will showcase:

  1. Loading and merging datasets.
  2. Data preprocessing:
    • Identifying duplicate columns (target).
    • Handling missing values (if any).
    • Dropping unnecessary features.
  3. Training two models:
    • Linear regression with L1 regularization (Lasso).
    • Linear regression with L2 regularization (Ridge).
  4. Evaluating and visualizing results.

Here’s the Python code for these steps:

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import Lasso, Ridge
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import matplotlib.pyplot as plt

# Step 1: Load datasets
train_df = pd.read_csv('/mnt/data/train.csv')
unique_df = pd.read_csv('/mnt/data/unique_m.csv')

# Step 2: Merge datasets and preprocess
merged_df = pd.concat([train_df, unique_df.iloc[:, 1:]], axis=1)  # Concatenate without duplicating headers
merged_df = merged_df.drop_duplicates()  # Drop duplicates, if any

# Drop the duplicate target column (assuming it's named 'target' in both datasets)
if 'critical_temp' in merged_df.columns:
    merged_df = merged_df.loc[:, ~merged_df.columns.duplicated()]

# Check for missing values
if merged_df.isnull().sum().sum() > 0:
    print("Handling missing values...")
    merged_df.fillna(merged_df.mean(), inplace=True)

# Define target and features
target = 'critical_temp'  # Replace with the correct column name
X = merged_df.drop(columns=[target])
y = merged_df[target]

# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Step 3: Train models
# L1 Regularization (Lasso)
lasso = Lasso(alpha=0.01, random_state=42)
lasso.fit(X_train, y_train)
lasso_predictions = lasso.predict(X_test)

# L2 Regularization (Ridge)
ridge = Ridge(alpha=0.01, random_state=42)
ridge.fit(X_train, y_train)
ridge_predictions = ridge.predict(X_test)

# Step 4: Evaluate models
def evaluate_model(name, y_true, y_pred):
    mae = mean_absolute_error(y_true, y_pred)
    mse = mean_squared_error(y_true, y_pred)
    r2 = r2_score(y_true, y_pred)
    print(f"{name} Model Evaluation:")
    print(f"Mean Absolute Error (MAE): {mae:.4f}")
    print(f"Mean Squared Error (MSE): {mse:.4f}")
    print(f"R^2 Score: {r2:.4f}")
    print("-" * 40)

evaluate_model("Lasso", y_test, lasso_predictions)
evaluate_model("Ridge", y_test, ridge_predictions)

# Step 5: Visualize results
plt.figure(figsize=(12, 6))

# Actual vs Predicted (Lasso)
plt.subplot(1, 2, 1)
plt.scatter(y_test, lasso_predictions, alpha=0.6, label='Lasso Predictions')
plt.plot([y.min(), y.max()], [y.min(), y.max()], 'r--', lw=2)
plt.title("Actual vs Predicted (Lasso)")
plt.xlabel("Actual")
plt.ylabel("Predicted")
plt.legend()

# Actual vs Predicted (Ridge)
plt.subplot(1, 2, 2)
plt.scatter(y_test, ridge_predictions, alpha=0.6, label='Ridge Predictions')
plt.plot([y.min(), y.max()], [y.min(), y.max()], 'r--', lw=2)
plt.title("Actual vs Predicted (Ridge)")
plt.xlabel("Actual")
plt.ylabel("Predicted")
plt.legend()

plt.tight_layout()
plt.show()

Key Outputs

  1. Metrics: MAE, MSE, and \(R^2\) scores for both Lasso and Ridge models.
  2. Visualizations:
    • Scatter plots showing actual vs predicted values for both models.
  3. Feature Importance:
    • For Lasso, coefficients with absolute values closer to 0 indicate less important features.
    • Ridge coefficients are shrunk but not eliminated.

Here’s an expanded approach to include hyperparameter tuning and cross-validation for both Lasso and Ridge regression models.

Updated Python Code:

from sklearn.model_selection import GridSearchCV, KFold

# Step 1: Cross-Validation Setup
kf = KFold(n_splits=5, shuffle=True, random_state=42)  # 5-fold cross-validation

# Step 2: Hyperparameter Tuning using GridSearchCV
# Define hyperparameter grid for Lasso and Ridge
lasso_param_grid = {'alpha': [0.001, 0.01, 0.1, 1, 10, 100]}
ridge_param_grid = {'alpha': [0.001, 0.01, 0.1, 1, 10, 100]}

# Lasso Regression with GridSearch
lasso_grid = GridSearchCV(Lasso(random_state=42), lasso_param_grid, scoring='neg_mean_absolute_error', cv=kf)
lasso_grid.fit(X_train, y_train)

# Ridge Regression with GridSearch
ridge_grid = GridSearchCV(Ridge(random_state=42), ridge_param_grid, scoring='neg_mean_absolute_error', cv=kf)
ridge_grid.fit(X_train, y_train)

# Step 3: Best Parameters and Re-training
best_lasso = lasso_grid.best_estimator_
best_ridge = ridge_grid.best_estimator_

# Retrain on the entire training set with the best parameters
best_lasso.fit(X_train, y_train)
best_ridge.fit(X_train, y_train)

# Predictions
lasso_predictions_cv = best_lasso.predict(X_test)
ridge_predictions_cv = best_ridge.predict(X_test)

# Step 4: Evaluate Models After Tuning
print("Best Lasso Alpha:", lasso_grid.best_params_['alpha'])
print("Best Ridge Alpha:", ridge_grid.best_params_['alpha'])

evaluate_model("Tuned Lasso", y_test, lasso_predictions_cv)
evaluate_model("Tuned Ridge", y_test, ridge_predictions_cv)

# Step 5: Visualize Feature Importance
plt.figure(figsize=(12, 6))

# Lasso Feature Importance
plt.subplot(1, 2, 1)
lasso_importance = pd.Series(np.abs(best_lasso.coef_), index=X.columns).sort_values(ascending=False)
lasso_importance.head(10).plot(kind='bar')
plt.title("Top 10 Features (Lasso)")
plt.ylabel("Coefficient Magnitude")

# Ridge Feature Importance
plt.subplot(1, 2, 2)
ridge_importance = pd.Series(np.abs(best_ridge.coef_), index=X.columns).sort_values(ascending=False)
ridge_importance.head(10).plot(kind='bar')
plt.title("Top 10 Features (Ridge)")
plt.ylabel("Coefficient Magnitude")

plt.tight_layout()
plt.show()

# Step 6: Cross-Validation Results Visualization
lasso_cv_results = pd.DataFrame(lasso_grid.cv_results_)
ridge_cv_results = pd.DataFrame(ridge_grid.cv_results_)

plt.figure(figsize=(10, 5))
plt.plot(lasso_cv_results['param_alpha'], -lasso_cv_results['mean_test_score'], label='Lasso')
plt.plot(ridge_cv_results['param_alpha'], -ridge_cv_results['mean_test_score'], label='Ridge')
plt.xscale('log')
plt.xlabel('Alpha (Regularization Strength)')
plt.ylabel('Negative Mean Absolute Error')
plt.title('Cross-Validation Performance for Lasso and Ridge')
plt.legend()
plt.show()

Code Explanation:

  1. Cross-Validation:
    • The KFold object splits the data into 5 folds, ensuring that each fold is used for testing exactly once.
    • This provides a more robust evaluation compared to a single train-test split.
  2. Hyperparameter Tuning:
    • GridSearchCV is used to search for the optimal alpha value for both Lasso and Ridge.
    • The scoring metric is neg_mean_absolute_error, where more negative values indicate worse performance.
  3. Feature Importance:
    • The magnitudes of the coefficients (absolute values) are plotted to visualize the top 10 most influential features for both models.
  4. Cross-Validation Results Visualization:
    • A line plot is used to show how the performance changes with different alpha values for both Lasso and Ridge.

Expected Outputs:

  1. Optimal Alpha Values:
    • Printed best alpha for both Lasso and Ridge from GridSearchCV.
  2. Model Metrics:
    • MAE, MSE, and \(R^2\) scores after tuning.
  3. Feature Importance:
    • Bar plots showing the top 10 features for each model.
  4. Cross-Validation Curve:
    • Performance (Negative MAE) plotted against different alpha values to observe the impact of regularization strength.

Step-by-Step Case Study Implementation

Following the detailed requirements and directives from the assignment and transcript, here’s a structured walkthrough for the case study:


1. Data Preparation

a) Loading and Merging Data:

import pandas as pd
from sklearn.preprocessing import StandardScaler

# Load datasets
train_df = pd.read_csv('/mnt/data/train.csv')
unique_df = pd.read_csv('/mnt/data/unique_m.csv')

# Merge datasets
merged_df = pd.concat([train_df, unique_df.iloc[:, 1:]], axis=1)

# Drop duplicate target columns (if applicable)
if 'critical_temp' in merged_df.columns:
    merged_df = merged_df.loc[:, ~merged_df.columns.duplicated()]

# Check for missing values
print("Missing values:", merged_df.isnull().sum().sum())

# Impute missing values (if any)
merged_df.fillna(merged_df.mean(), inplace=True)

# Describe data size
print(f"Dataset contains {merged_df.shape[0]} rows and {merged_df.shape[1]} columns.")

b) Feature Selection:

# Calculate correlation matrix and drop highly correlated features
correlation_matrix = merged_df.corr()
high_corr_features = correlation_matrix[correlation_matrix > 0.95].stack().index
merged_df.drop(columns=[col for col, _ in high_corr_features if col != _], inplace=True)

# Scale features
scaler = StandardScaler()
X = scaler.fit_transform(merged_df.drop(columns=['critical_temp']))
y = merged_df['critical_temp']

2. Splitting Data and Cross-Validation

from sklearn.model_selection import train_test_split, KFold

# Train-test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 5-fold cross-validation setup
kf = KFold(n_splits=5, shuffle=True, random_state=42)

3. Model Training and Hyperparameter Tuning

a) Define Elastic Net Model and Hyperparameter Grid:

from sklearn.linear_model import ElasticNet
from sklearn.model_selection import GridSearchCV

# Define parameter grid
param_grid = {
    'alpha': [0.001, 0.01, 0.1, 1, 10],
    'l1_ratio': [0.1, 0.5, 0.7, 0.9, 1.0]
}

# Elastic Net with GridSearchCV
elastic_net = GridSearchCV(ElasticNet(random_state=42), param_grid, cv=kf, scoring='neg_mean_absolute_error')
elastic_net.fit(X_train, y_train)

# Best hyperparameters
print("Best Hyperparameters:", elastic_net.best_params_)

4. Model Evaluation

a) Metrics and Visualizations:

from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import matplotlib.pyplot as plt

# Predictions
y_pred = elastic_net.predict(X_test)

# Metrics
mae = mean_absolute_error(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)

print(f"Mean Absolute Error: {mae:.4f}")
print(f"Mean Squared Error: {mse:.4f}")
print(f"R^2 Score: {r2:.4f}")

# Visualizations
plt.figure(figsize=(12, 6))

# Actual vs Predicted
plt.subplot(1, 2, 1)
plt.scatter(y_test, y_pred, alpha=0.6)
plt.plot([y.min(), y.max()], [y.min(), y.max()], 'r--')
plt.title("Actual vs Predicted")
plt.xlabel("Actual")
plt.ylabel("Predicted")

# Residuals
residuals = y_test - y_pred
plt.subplot(1, 2, 2)
plt.scatter(y_pred, residuals, alpha=0.6)
plt.axhline(0, color='r', linestyle='--')
plt.title("Residuals vs Predicted")
plt.xlabel("Predicted")
plt.ylabel("Residuals")

plt.tight_layout()
plt.show()

5. Feature Importance

# Feature importance from Elastic Net
import numpy as np

importance = np.abs(elastic_net.best_estimator_.coef_)
sorted_idx = np.argsort(importance)[::-1]
top_features = np.array(merged_df.columns[:-1])[sorted_idx[:20]]  # Top 20 features

# Bar plot
plt.figure(figsize=(10, 6))
plt.barh(top_features, importance[sorted_idx[:20]])
plt.gca().invert_yaxis()
plt.title("Top 20 Feature Importances (Elastic Net)")
plt.xlabel("Coefficient Magnitude")
plt.show()

6. Regularization Strength vs MAE

# Analyze effect of alpha on MAE
results = elastic_net.cv_results_
alpha_values = [params['alpha'] for params in results['params']]
mae_values = -results['mean_test_score']

plt.figure(figsize=(8, 5))
plt.plot(alpha_values, mae_values, marker='o')
plt.xscale('log')
plt.xlabel("Alpha (Regularization Strength)")
plt.ylabel("Negative Mean Absolute Error")
plt.title("Effect of Alpha on MAE")
plt.show()

GPU Utilization

For additional acceleration using GPU (if available), we can implement this using PyTorch

LS0tDQp0aXRsZTogIlFUVyBDbGFzcyAxIGludG8gQ2FzZSBTdHVkeSAxIg0KYXV0aG9yOiBKZXNzaWNhIE1jUGhhdWwNCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCkNsYXNzIDEgdHJhbnNjcmlwdCBzdW1tYXJpemVkIGFuZCAgYnJva2VuIGRvd24gaW50byBzZWN0aW9ucyBnb2luZyBpbnRvIGNhc2Ugc3R1ZGZ5IDEuIA0KDQoxLiAqKkNvdXJzZSBJbnRyb2R1Y3Rpb24gYW5kIFN0cnVjdHVyZSoqDQogICAtIEluc3RydWN0b3I6IFByb2Zlc3NvciBTbGF0ZXIuDQogICAtIENvdXJzZTogIlF1YW50aWZ5aW5nIHRoZSBXb3JsZC4iDQogICAtIFN5bGxhYnVzIGFuZCBUZXh0Ym9vazoNCiAgICAgLSBGcmVlIG9ubGluZSB0ZXh0Ym9vayBhdmFpbGFibGUuDQogICAtIEtleSBDb21wb25lbnRzIG9mIEdyYWRpbmc6DQogICAgIC0gQ2FzZSBzdHVkaWVzICg1MCUpOiBTZXZlbiBiaXdlZWtseSBjYXNlIHN0dWRpZXMgKG9kZCB3ZWVrcykuIEZpbmFsIGNhc2Ugc3R1ZHkgY291bnRzIGRvdWJsZS4NCiAgICAgLSBQYXJ0aWNpcGF0aW9uICgzMCUpOiBDYW1lcmEgb24gZHVyaW5nIGNsYXNzLCBhbmQgcHJlLXNlc3Npb24gc3VibWlzc2lvbnMuDQogICAgIC0gUXVpenplcyAoMjAlKTogV2Vla2x5LCBkdWUgVHVlc2RheSBtaWRuaWdodCwgYXV0by1ncmFkZWQuDQogICAtIExhdGUgU3VibWlzc2lvbiBQb2xpY3k6DQogICAgIC0gQ2FzZSBzdHVkaWVzOiAxMCUgZGVkdWN0aW9uIHBlciBkYXkuDQogICAgIC0gUHJlLXNlc3Npb24gYW5kIHF1aXp6ZXM6IE5vIGxhdGUgc3VibWlzc2lvbnMgYWNjZXB0ZWQuDQoNCjIuICoqQ2FzZSBTdHVkeSBSZXF1aXJlbWVudHMqKg0KICAgLSBGb3JtYXQ6DQogICAgIC0gVGVjaG5pY2FsIHdyaXRlLXVwIGFpbWVkIGF0IGV4cGxhaW5pbmcgZGF0YSBzY2llbmNlIGFuYWx5c2lzLg0KICAgICAtIEluY2x1ZGUgYWxsIHN0ZXBzLCBzdWNoIGFzIGRhdGEgcHJlcGFyYXRpb24sIG1vZGVsIHNlbGVjdGlvbiwgYW5kIHJlc3VsdHMuDQogICAtIEtleSBFbGVtZW50czoNCiAgICAgLSBEYXRhIGNsZWFuaW5nIChlLmcuLCBpbXB1dGF0aW9uLCBmZWF0dXJlIGhhbmRsaW5nKS4NCiAgICAgLSBNb2RlbCBjaG9pY2UgYW5kIGNvbmZpZ3VyYXRpb24uDQogICAgIC0gTWV0cmljcyBsaWtlIGxvc3MgZnVuY3Rpb24sIGFjY3VyYWN5LCBwcmVjaXNpb24sIHJlY2FsbCwgZXRjLg0KICAgICAtIFZpc3VhbGl6YXRpb25zIChyZXNpZHVhbCBwbG90cywgY29uZnVzaW9uIG1hdHJpeCwgZXRjLikuDQogICAtIE1hdGhlbWF0aWNhbCBSZXByZXNlbnRhdGlvbnM6DQogICAgIC0gQ3Jvc3MtdmFsaWRhdGlvbjogU3BsaXQgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMsIHJvdGF0aW5nIHRlc3Qgc2V0cyBmb3IgdW5iaWFzZWQgZXZhbHVhdGlvbi4NCiAgICAgLSBSZWd1bGFyaXphdGlvbjoNCiAgICAgICAtIEwxIChMYXNzbyk6IFwoIFx0ZXh0e0xvc3N9ID0gfHx5IC0gWFxiZXRhfHxeMl8yICsgXGxhbWJkYSB8fFxiZXRhfHxfMSBcKQ0KICAgICAgIC0gTDIgKFJpZGdlKTogXCggXHRleHR7TG9zc30gPSB8fHkgLSBYXGJldGF8fF4yXzIgKyBcbGFtYmRhIHx8XGJldGF8fF4yXzIgXCkNCg0KMy4gKipQeXRob24tU3BlY2lmaWMgQ29uY2VwdHMqKg0KICAgLSBQeXRob24ncyBTdHJlbmd0aHM6DQogICAgIC0gVmVyc2F0aWxpdHkgYWNyb3NzIGRhdGEgc2NpZW5jZSBhbmQgZ2VuZXJhbCBwcm9ncmFtbWluZyB0YXNrcy4NCiAgICAgLSBLZXkgbGlicmFyaWVzOiBOdW1QeSwgUGFuZGFzLCBQeVRvcmNoLg0KICAgLSBQeXRob24gSW5kZXhpbmc6DQogICAgIC0gWmVyby1iYXNlZCBpbmRleGluZzogYGxpc3RbMF1gIHJlZmVycyB0byB0aGUgZmlyc3QgZWxlbWVudC4NCiAgICAgLSBTbGljaW5nOiBgbGlzdFtzdGFydDplbmRdYCBpbmNsdWRlcyBgc3RhcnRgIGJ1dCBleGNsdWRlcyBgZW5kYC4NCiAgIC0gRW52aXJvbm1lbnRzOg0KICAgICAtIEltcG9ydGFuY2Ugb2YgdXNpbmcgdmlydHVhbCBlbnZpcm9ubWVudHMgdG8gbWFuYWdlIFB5dGhvbiBkZXBlbmRlbmNpZXMuDQoNCjQuICoqTWF0aGVtYXRpY2FsIFByaW5jaXBsZXMgZm9yIEFuYWx5c2lzKioNCiAgIC0gTWV0cmljczoNCiAgICAgLSBNZWFuIEFic29sdXRlIEVycm9yIChNQUUpOiBcKCBNQUUgPSBcZnJhY3sxfXtufSBcc3VtX3tpPTF9Xm4gfHlfaSAtIFxoYXR7eX1faXwgXCkNCiAgICAgLSBQcmVjaXNpb24sIFJlY2FsbCwgU2Vuc2l0aXZpdHksIFNwZWNpZmljaXR5Lg0KICAgLSBEYXRhIEhhbmRsaW5nOg0KICAgICAtIEZlYXR1cmUgc2VsZWN0aW9uIHVzaW5nIEwxIHJlZ3VsYXJpemF0aW9uLg0KICAgICAtIEhhbmRsaW5nIGhpZ2hseSBjb3JyZWxhdGVkIGZlYXR1cmVzOiBBdm9pZCBkcm9wcGluZyB1bmxlc3MgMTAwJSBjb3JyZWxhdGVkIG9yIHVuaW5mb3JtYXRpdmUuDQoNCjUuICoqQ2FzZSBTdHVkeSBFeGFtcGxlOiBTdXBlcmNvbmR1Y3RvcnMqKg0KICAgLSBEYXRhc2V0Og0KICAgICAtIENvbnRhaW5zIGNoZW1pY2FsIGNvbXBvc2l0aW9ucyBhbmQgdGhlaXIgY3JpdGljYWwgdGVtcGVyYXR1cmVzLg0KICAgICAtIFRhc2tzOg0KICAgICAgIC0gTWVyZ2UgdHJhaW5pbmcgYW5kIHVuaXF1ZSBkYXRhc2V0cy4NCiAgICAgICAtIEhhbmRsZSBkdXBsaWNhdGUgdGFyZ2V0IGNvbHVtbnMuDQogICAgICAgLSBUcmFpbiBtb2RlbHMgdXNpbmcgTDEgYW5kIEwyIHJlZ3VsYXJpemF0aW9uLg0KICAgLSBWaXN1YWxpemF0aW9uczoNCiAgICAgLSBJbmNsdWRlIGNsZWFyLCBsZWdpYmxlIGdyYXBocyBsaWtlIGZlYXR1cmUgaW1wb3J0YW5jZSBhbmQgcmVzaWR1YWwgcGxvdHMuDQoNCjYuICoqS2V5IFRha2Vhd2F5cyBhbmQgUmVjb21tZW5kYXRpb25zKioNCiAgIC0gQXZvaWQgc3ViamVjdGl2ZSB0ZXJtcyBsaWtlICJwZXJmb3JtZWQgd2VsbCLigJR1c2UgcHJlY2lzZSBtZXRyaWNzLg0KICAgLSBBbHdheXMgY3Jvc3MtdmFsaWRhdGUgbW9kZWxzIHVubGVzcyBjb21wdXRhdGlvbmFsbHkgcHJvaGliaXRpdmUuDQogICAtIFN1Ym1pdCBhbGwgcmVsZXZhbnQgY29kZSBmb3IgdHJhbnNwYXJlbmN5LCBldmVuIGlmIHVub3JnYW5pemVkLg0KDQpUbyBleHBhbmQgb24gdGhlIGNhc2Ugc3R1ZHkgYW5kIGRlbW9uc3RyYXRlIHRoZSBjb25jZXB0cyB1c2luZyB0aGUgcHJvdmlkZWQgZGF0YSAoYHRyYWluLmNzdmAgYW5kIGB1bmlxdWVfbS5jc3ZgKSwgSSB3aWxsIHNob3djYXNlOg0KDQoxLiAqKkxvYWRpbmcgYW5kIG1lcmdpbmcgZGF0YXNldHMqKi4NCjIuICoqRGF0YSBwcmVwcm9jZXNzaW5nKio6DQogICAtIElkZW50aWZ5aW5nIGR1cGxpY2F0ZSBjb2x1bW5zICh0YXJnZXQpLg0KICAgLSBIYW5kbGluZyBtaXNzaW5nIHZhbHVlcyAoaWYgYW55KS4NCiAgIC0gRHJvcHBpbmcgdW5uZWNlc3NhcnkgZmVhdHVyZXMuDQozLiAqKlRyYWluaW5nIHR3byBtb2RlbHMqKjoNCiAgIC0gTGluZWFyIHJlZ3Jlc3Npb24gd2l0aCBMMSByZWd1bGFyaXphdGlvbiAoTGFzc28pLg0KICAgLSBMaW5lYXIgcmVncmVzc2lvbiB3aXRoIEwyIHJlZ3VsYXJpemF0aW9uIChSaWRnZSkuDQo0LiAqKkV2YWx1YXRpbmcgYW5kIHZpc3VhbGl6aW5nIHJlc3VsdHMqKi4NCg0KSGVyZeKAmXMgdGhlIFB5dGhvbiBjb2RlIGZvciB0aGVzZSBzdGVwczoNCg0KYGBgcHl0aG9uDQppbXBvcnQgcGFuZGFzIGFzIHBkDQppbXBvcnQgbnVtcHkgYXMgbnANCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQsIGNyb3NzX3ZhbF9zY29yZQ0KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgTGFzc28sIFJpZGdlDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgbWVhbl9hYnNvbHV0ZV9lcnJvciwgbWVhbl9zcXVhcmVkX2Vycm9yLCByMl9zY29yZQ0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQojIFN0ZXAgMTogTG9hZCBkYXRhc2V0cw0KdHJhaW5fZGYgPSBwZC5yZWFkX2NzdignL21udC9kYXRhL3RyYWluLmNzdicpDQp1bmlxdWVfZGYgPSBwZC5yZWFkX2NzdignL21udC9kYXRhL3VuaXF1ZV9tLmNzdicpDQoNCiMgU3RlcCAyOiBNZXJnZSBkYXRhc2V0cyBhbmQgcHJlcHJvY2Vzcw0KbWVyZ2VkX2RmID0gcGQuY29uY2F0KFt0cmFpbl9kZiwgdW5pcXVlX2RmLmlsb2NbOiwgMTpdXSwgYXhpcz0xKSAgIyBDb25jYXRlbmF0ZSB3aXRob3V0IGR1cGxpY2F0aW5nIGhlYWRlcnMNCm1lcmdlZF9kZiA9IG1lcmdlZF9kZi5kcm9wX2R1cGxpY2F0ZXMoKSAgIyBEcm9wIGR1cGxpY2F0ZXMsIGlmIGFueQ0KDQojIERyb3AgdGhlIGR1cGxpY2F0ZSB0YXJnZXQgY29sdW1uIChhc3N1bWluZyBpdCdzIG5hbWVkICd0YXJnZXQnIGluIGJvdGggZGF0YXNldHMpDQppZiAnY3JpdGljYWxfdGVtcCcgaW4gbWVyZ2VkX2RmLmNvbHVtbnM6DQogICAgbWVyZ2VkX2RmID0gbWVyZ2VkX2RmLmxvY1s6LCB+bWVyZ2VkX2RmLmNvbHVtbnMuZHVwbGljYXRlZCgpXQ0KDQojIENoZWNrIGZvciBtaXNzaW5nIHZhbHVlcw0KaWYgbWVyZ2VkX2RmLmlzbnVsbCgpLnN1bSgpLnN1bSgpID4gMDoNCiAgICBwcmludCgiSGFuZGxpbmcgbWlzc2luZyB2YWx1ZXMuLi4iKQ0KICAgIG1lcmdlZF9kZi5maWxsbmEobWVyZ2VkX2RmLm1lYW4oKSwgaW5wbGFjZT1UcnVlKQ0KDQojIERlZmluZSB0YXJnZXQgYW5kIGZlYXR1cmVzDQp0YXJnZXQgPSAnY3JpdGljYWxfdGVtcCcgICMgUmVwbGFjZSB3aXRoIHRoZSBjb3JyZWN0IGNvbHVtbiBuYW1lDQpYID0gbWVyZ2VkX2RmLmRyb3AoY29sdW1ucz1bdGFyZ2V0XSkNCnkgPSBtZXJnZWRfZGZbdGFyZ2V0XQ0KDQojIFNwbGl0IGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzDQpYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoWCwgeSwgdGVzdF9zaXplPTAuMiwgcmFuZG9tX3N0YXRlPTQyKQ0KDQojIFN0ZXAgMzogVHJhaW4gbW9kZWxzDQojIEwxIFJlZ3VsYXJpemF0aW9uIChMYXNzbykNCmxhc3NvID0gTGFzc28oYWxwaGE9MC4wMSwgcmFuZG9tX3N0YXRlPTQyKQ0KbGFzc28uZml0KFhfdHJhaW4sIHlfdHJhaW4pDQpsYXNzb19wcmVkaWN0aW9ucyA9IGxhc3NvLnByZWRpY3QoWF90ZXN0KQ0KDQojIEwyIFJlZ3VsYXJpemF0aW9uIChSaWRnZSkNCnJpZGdlID0gUmlkZ2UoYWxwaGE9MC4wMSwgcmFuZG9tX3N0YXRlPTQyKQ0KcmlkZ2UuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQpyaWRnZV9wcmVkaWN0aW9ucyA9IHJpZGdlLnByZWRpY3QoWF90ZXN0KQ0KDQojIFN0ZXAgNDogRXZhbHVhdGUgbW9kZWxzDQpkZWYgZXZhbHVhdGVfbW9kZWwobmFtZSwgeV90cnVlLCB5X3ByZWQpOg0KICAgIG1hZSA9IG1lYW5fYWJzb2x1dGVfZXJyb3IoeV90cnVlLCB5X3ByZWQpDQogICAgbXNlID0gbWVhbl9zcXVhcmVkX2Vycm9yKHlfdHJ1ZSwgeV9wcmVkKQ0KICAgIHIyID0gcjJfc2NvcmUoeV90cnVlLCB5X3ByZWQpDQogICAgcHJpbnQoZiJ7bmFtZX0gTW9kZWwgRXZhbHVhdGlvbjoiKQ0KICAgIHByaW50KGYiTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKToge21hZTouNGZ9IikNCiAgICBwcmludChmIk1lYW4gU3F1YXJlZCBFcnJvciAoTVNFKToge21zZTouNGZ9IikNCiAgICBwcmludChmIlJeMiBTY29yZToge3IyOi40Zn0iKQ0KICAgIHByaW50KCItIiAqIDQwKQ0KDQpldmFsdWF0ZV9tb2RlbCgiTGFzc28iLCB5X3Rlc3QsIGxhc3NvX3ByZWRpY3Rpb25zKQ0KZXZhbHVhdGVfbW9kZWwoIlJpZGdlIiwgeV90ZXN0LCByaWRnZV9wcmVkaWN0aW9ucykNCg0KIyBTdGVwIDU6IFZpc3VhbGl6ZSByZXN1bHRzDQpwbHQuZmlndXJlKGZpZ3NpemU9KDEyLCA2KSkNCg0KIyBBY3R1YWwgdnMgUHJlZGljdGVkIChMYXNzbykNCnBsdC5zdWJwbG90KDEsIDIsIDEpDQpwbHQuc2NhdHRlcih5X3Rlc3QsIGxhc3NvX3ByZWRpY3Rpb25zLCBhbHBoYT0wLjYsIGxhYmVsPSdMYXNzbyBQcmVkaWN0aW9ucycpDQpwbHQucGxvdChbeS5taW4oKSwgeS5tYXgoKV0sIFt5Lm1pbigpLCB5Lm1heCgpXSwgJ3ItLScsIGx3PTIpDQpwbHQudGl0bGUoIkFjdHVhbCB2cyBQcmVkaWN0ZWQgKExhc3NvKSIpDQpwbHQueGxhYmVsKCJBY3R1YWwiKQ0KcGx0LnlsYWJlbCgiUHJlZGljdGVkIikNCnBsdC5sZWdlbmQoKQ0KDQojIEFjdHVhbCB2cyBQcmVkaWN0ZWQgKFJpZGdlKQ0KcGx0LnN1YnBsb3QoMSwgMiwgMikNCnBsdC5zY2F0dGVyKHlfdGVzdCwgcmlkZ2VfcHJlZGljdGlvbnMsIGFscGhhPTAuNiwgbGFiZWw9J1JpZGdlIFByZWRpY3Rpb25zJykNCnBsdC5wbG90KFt5Lm1pbigpLCB5Lm1heCgpXSwgW3kubWluKCksIHkubWF4KCldLCAnci0tJywgbHc9MikNCnBsdC50aXRsZSgiQWN0dWFsIHZzIFByZWRpY3RlZCAoUmlkZ2UpIikNCnBsdC54bGFiZWwoIkFjdHVhbCIpDQpwbHQueWxhYmVsKCJQcmVkaWN0ZWQiKQ0KcGx0LmxlZ2VuZCgpDQoNCnBsdC50aWdodF9sYXlvdXQoKQ0KcGx0LnNob3coKQ0KYGBgDQoNCiMjIyBLZXkgT3V0cHV0cw0KMS4gKipNZXRyaWNzKio6IE1BRSwgTVNFLCBhbmQgXChSXjJcKSBzY29yZXMgZm9yIGJvdGggTGFzc28gYW5kIFJpZGdlIG1vZGVscy4NCjIuICoqVmlzdWFsaXphdGlvbnMqKjoNCiAgIC0gU2NhdHRlciBwbG90cyBzaG93aW5nIGFjdHVhbCB2cyBwcmVkaWN0ZWQgdmFsdWVzIGZvciBib3RoIG1vZGVscy4NCjMuICoqRmVhdHVyZSBJbXBvcnRhbmNlKio6DQogICAtIEZvciBMYXNzbywgY29lZmZpY2llbnRzIHdpdGggYWJzb2x1dGUgdmFsdWVzIGNsb3NlciB0byAwIGluZGljYXRlIGxlc3MgaW1wb3J0YW50IGZlYXR1cmVzLg0KICAgLSBSaWRnZSBjb2VmZmljaWVudHMgYXJlIHNocnVuayBidXQgbm90IGVsaW1pbmF0ZWQuDQoNCkhlcmUncyBhbiBleHBhbmRlZCBhcHByb2FjaCB0byBpbmNsdWRlICoqaHlwZXJwYXJhbWV0ZXIgdHVuaW5nKiogYW5kICoqY3Jvc3MtdmFsaWRhdGlvbioqIGZvciBib3RoIExhc3NvIGFuZCBSaWRnZSByZWdyZXNzaW9uIG1vZGVscy4NCg0KIyMjIFVwZGF0ZWQgUHl0aG9uIENvZGU6DQoNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgR3JpZFNlYXJjaENWLCBLRm9sZA0KDQojIFN0ZXAgMTogQ3Jvc3MtVmFsaWRhdGlvbiBTZXR1cA0Ka2YgPSBLRm9sZChuX3NwbGl0cz01LCBzaHVmZmxlPVRydWUsIHJhbmRvbV9zdGF0ZT00MikgICMgNS1mb2xkIGNyb3NzLXZhbGlkYXRpb24NCg0KIyBTdGVwIDI6IEh5cGVycGFyYW1ldGVyIFR1bmluZyB1c2luZyBHcmlkU2VhcmNoQ1YNCiMgRGVmaW5lIGh5cGVycGFyYW1ldGVyIGdyaWQgZm9yIExhc3NvIGFuZCBSaWRnZQ0KbGFzc29fcGFyYW1fZ3JpZCA9IHsnYWxwaGEnOiBbMC4wMDEsIDAuMDEsIDAuMSwgMSwgMTAsIDEwMF19DQpyaWRnZV9wYXJhbV9ncmlkID0geydhbHBoYSc6IFswLjAwMSwgMC4wMSwgMC4xLCAxLCAxMCwgMTAwXX0NCg0KIyBMYXNzbyBSZWdyZXNzaW9uIHdpdGggR3JpZFNlYXJjaA0KbGFzc29fZ3JpZCA9IEdyaWRTZWFyY2hDVihMYXNzbyhyYW5kb21fc3RhdGU9NDIpLCBsYXNzb19wYXJhbV9ncmlkLCBzY29yaW5nPSduZWdfbWVhbl9hYnNvbHV0ZV9lcnJvcicsIGN2PWtmKQ0KbGFzc29fZ3JpZC5maXQoWF90cmFpbiwgeV90cmFpbikNCg0KIyBSaWRnZSBSZWdyZXNzaW9uIHdpdGggR3JpZFNlYXJjaA0KcmlkZ2VfZ3JpZCA9IEdyaWRTZWFyY2hDVihSaWRnZShyYW5kb21fc3RhdGU9NDIpLCByaWRnZV9wYXJhbV9ncmlkLCBzY29yaW5nPSduZWdfbWVhbl9hYnNvbHV0ZV9lcnJvcicsIGN2PWtmKQ0KcmlkZ2VfZ3JpZC5maXQoWF90cmFpbiwgeV90cmFpbikNCg0KIyBTdGVwIDM6IEJlc3QgUGFyYW1ldGVycyBhbmQgUmUtdHJhaW5pbmcNCmJlc3RfbGFzc28gPSBsYXNzb19ncmlkLmJlc3RfZXN0aW1hdG9yXw0KYmVzdF9yaWRnZSA9IHJpZGdlX2dyaWQuYmVzdF9lc3RpbWF0b3JfDQoNCiMgUmV0cmFpbiBvbiB0aGUgZW50aXJlIHRyYWluaW5nIHNldCB3aXRoIHRoZSBiZXN0IHBhcmFtZXRlcnMNCmJlc3RfbGFzc28uZml0KFhfdHJhaW4sIHlfdHJhaW4pDQpiZXN0X3JpZGdlLmZpdChYX3RyYWluLCB5X3RyYWluKQ0KDQojIFByZWRpY3Rpb25zDQpsYXNzb19wcmVkaWN0aW9uc19jdiA9IGJlc3RfbGFzc28ucHJlZGljdChYX3Rlc3QpDQpyaWRnZV9wcmVkaWN0aW9uc19jdiA9IGJlc3RfcmlkZ2UucHJlZGljdChYX3Rlc3QpDQoNCiMgU3RlcCA0OiBFdmFsdWF0ZSBNb2RlbHMgQWZ0ZXIgVHVuaW5nDQpwcmludCgiQmVzdCBMYXNzbyBBbHBoYToiLCBsYXNzb19ncmlkLmJlc3RfcGFyYW1zX1snYWxwaGEnXSkNCnByaW50KCJCZXN0IFJpZGdlIEFscGhhOiIsIHJpZGdlX2dyaWQuYmVzdF9wYXJhbXNfWydhbHBoYSddKQ0KDQpldmFsdWF0ZV9tb2RlbCgiVHVuZWQgTGFzc28iLCB5X3Rlc3QsIGxhc3NvX3ByZWRpY3Rpb25zX2N2KQ0KZXZhbHVhdGVfbW9kZWwoIlR1bmVkIFJpZGdlIiwgeV90ZXN0LCByaWRnZV9wcmVkaWN0aW9uc19jdikNCg0KIyBTdGVwIDU6IFZpc3VhbGl6ZSBGZWF0dXJlIEltcG9ydGFuY2UNCnBsdC5maWd1cmUoZmlnc2l6ZT0oMTIsIDYpKQ0KDQojIExhc3NvIEZlYXR1cmUgSW1wb3J0YW5jZQ0KcGx0LnN1YnBsb3QoMSwgMiwgMSkNCmxhc3NvX2ltcG9ydGFuY2UgPSBwZC5TZXJpZXMobnAuYWJzKGJlc3RfbGFzc28uY29lZl8pLCBpbmRleD1YLmNvbHVtbnMpLnNvcnRfdmFsdWVzKGFzY2VuZGluZz1GYWxzZSkNCmxhc3NvX2ltcG9ydGFuY2UuaGVhZCgxMCkucGxvdChraW5kPSdiYXInKQ0KcGx0LnRpdGxlKCJUb3AgMTAgRmVhdHVyZXMgKExhc3NvKSIpDQpwbHQueWxhYmVsKCJDb2VmZmljaWVudCBNYWduaXR1ZGUiKQ0KDQojIFJpZGdlIEZlYXR1cmUgSW1wb3J0YW5jZQ0KcGx0LnN1YnBsb3QoMSwgMiwgMikNCnJpZGdlX2ltcG9ydGFuY2UgPSBwZC5TZXJpZXMobnAuYWJzKGJlc3RfcmlkZ2UuY29lZl8pLCBpbmRleD1YLmNvbHVtbnMpLnNvcnRfdmFsdWVzKGFzY2VuZGluZz1GYWxzZSkNCnJpZGdlX2ltcG9ydGFuY2UuaGVhZCgxMCkucGxvdChraW5kPSdiYXInKQ0KcGx0LnRpdGxlKCJUb3AgMTAgRmVhdHVyZXMgKFJpZGdlKSIpDQpwbHQueWxhYmVsKCJDb2VmZmljaWVudCBNYWduaXR1ZGUiKQ0KDQpwbHQudGlnaHRfbGF5b3V0KCkNCnBsdC5zaG93KCkNCg0KIyBTdGVwIDY6IENyb3NzLVZhbGlkYXRpb24gUmVzdWx0cyBWaXN1YWxpemF0aW9uDQpsYXNzb19jdl9yZXN1bHRzID0gcGQuRGF0YUZyYW1lKGxhc3NvX2dyaWQuY3ZfcmVzdWx0c18pDQpyaWRnZV9jdl9yZXN1bHRzID0gcGQuRGF0YUZyYW1lKHJpZGdlX2dyaWQuY3ZfcmVzdWx0c18pDQoNCnBsdC5maWd1cmUoZmlnc2l6ZT0oMTAsIDUpKQ0KcGx0LnBsb3QobGFzc29fY3ZfcmVzdWx0c1sncGFyYW1fYWxwaGEnXSwgLWxhc3NvX2N2X3Jlc3VsdHNbJ21lYW5fdGVzdF9zY29yZSddLCBsYWJlbD0nTGFzc28nKQ0KcGx0LnBsb3QocmlkZ2VfY3ZfcmVzdWx0c1sncGFyYW1fYWxwaGEnXSwgLXJpZGdlX2N2X3Jlc3VsdHNbJ21lYW5fdGVzdF9zY29yZSddLCBsYWJlbD0nUmlkZ2UnKQ0KcGx0LnhzY2FsZSgnbG9nJykNCnBsdC54bGFiZWwoJ0FscGhhIChSZWd1bGFyaXphdGlvbiBTdHJlbmd0aCknKQ0KcGx0LnlsYWJlbCgnTmVnYXRpdmUgTWVhbiBBYnNvbHV0ZSBFcnJvcicpDQpwbHQudGl0bGUoJ0Nyb3NzLVZhbGlkYXRpb24gUGVyZm9ybWFuY2UgZm9yIExhc3NvIGFuZCBSaWRnZScpDQpwbHQubGVnZW5kKCkNCnBsdC5zaG93KCkNCmBgYA0KDQojIyMgQ29kZSBFeHBsYW5hdGlvbjoNCg0KMS4gKipDcm9zcy1WYWxpZGF0aW9uKio6DQogICAtIFRoZSBgS0ZvbGRgIG9iamVjdCBzcGxpdHMgdGhlIGRhdGEgaW50byA1IGZvbGRzLCBlbnN1cmluZyB0aGF0IGVhY2ggZm9sZCBpcyB1c2VkIGZvciB0ZXN0aW5nIGV4YWN0bHkgb25jZS4NCiAgIC0gVGhpcyBwcm92aWRlcyBhIG1vcmUgcm9idXN0IGV2YWx1YXRpb24gY29tcGFyZWQgdG8gYSBzaW5nbGUgdHJhaW4tdGVzdCBzcGxpdC4NCg0KMi4gKipIeXBlcnBhcmFtZXRlciBUdW5pbmcqKjoNCiAgIC0gYEdyaWRTZWFyY2hDVmAgaXMgdXNlZCB0byBzZWFyY2ggZm9yIHRoZSBvcHRpbWFsIGBhbHBoYWAgdmFsdWUgZm9yIGJvdGggTGFzc28gYW5kIFJpZGdlLg0KICAgLSBUaGUgc2NvcmluZyBtZXRyaWMgaXMgYG5lZ19tZWFuX2Fic29sdXRlX2Vycm9yYCwgd2hlcmUgbW9yZSBuZWdhdGl2ZSB2YWx1ZXMgaW5kaWNhdGUgd29yc2UgcGVyZm9ybWFuY2UuDQoNCjMuICoqRmVhdHVyZSBJbXBvcnRhbmNlKio6DQogICAtIFRoZSBtYWduaXR1ZGVzIG9mIHRoZSBjb2VmZmljaWVudHMgKGFic29sdXRlIHZhbHVlcykgYXJlIHBsb3R0ZWQgdG8gdmlzdWFsaXplIHRoZSB0b3AgMTAgbW9zdCBpbmZsdWVudGlhbCBmZWF0dXJlcyBmb3IgYm90aCBtb2RlbHMuDQoNCjQuICoqQ3Jvc3MtVmFsaWRhdGlvbiBSZXN1bHRzIFZpc3VhbGl6YXRpb24qKjoNCiAgIC0gQSBsaW5lIHBsb3QgaXMgdXNlZCB0byBzaG93IGhvdyB0aGUgcGVyZm9ybWFuY2UgY2hhbmdlcyB3aXRoIGRpZmZlcmVudCBgYWxwaGFgIHZhbHVlcyBmb3IgYm90aCBMYXNzbyBhbmQgUmlkZ2UuDQoNCiMjIyBFeHBlY3RlZCBPdXRwdXRzOg0KDQoxLiAqKk9wdGltYWwgQWxwaGEgVmFsdWVzKio6DQogICAtIFByaW50ZWQgYmVzdCBgYWxwaGFgIGZvciBib3RoIExhc3NvIGFuZCBSaWRnZSBmcm9tIGBHcmlkU2VhcmNoQ1ZgLg0KDQoyLiAqKk1vZGVsIE1ldHJpY3MqKjoNCiAgIC0gTUFFLCBNU0UsIGFuZCBcKFJeMlwpIHNjb3JlcyBhZnRlciB0dW5pbmcuDQoNCjMuICoqRmVhdHVyZSBJbXBvcnRhbmNlKio6DQogICAtIEJhciBwbG90cyBzaG93aW5nIHRoZSB0b3AgMTAgZmVhdHVyZXMgZm9yIGVhY2ggbW9kZWwuDQoNCjQuICoqQ3Jvc3MtVmFsaWRhdGlvbiBDdXJ2ZSoqOg0KICAgLSBQZXJmb3JtYW5jZSAoTmVnYXRpdmUgTUFFKSBwbG90dGVkIGFnYWluc3QgZGlmZmVyZW50IGBhbHBoYWAgdmFsdWVzIHRvIG9ic2VydmUgdGhlIGltcGFjdCBvZiByZWd1bGFyaXphdGlvbiBzdHJlbmd0aC4NCg0KDQoNCg0KDQojIyMgU3RlcC1ieS1TdGVwIENhc2UgU3R1ZHkgSW1wbGVtZW50YXRpb24NCg0KRm9sbG93aW5nIHRoZSBkZXRhaWxlZCByZXF1aXJlbWVudHMgYW5kIGRpcmVjdGl2ZXMgZnJvbSB0aGUgYXNzaWdubWVudCBhbmQgdHJhbnNjcmlwdCwgaGVyZeKAmXMgYSBzdHJ1Y3R1cmVkIHdhbGt0aHJvdWdoIGZvciB0aGUgY2FzZSBzdHVkeToNCg0KLS0tDQoNCiMjIyAqKjEuIERhdGEgUHJlcGFyYXRpb24qKg0KIyMjIyBhKSBMb2FkaW5nIGFuZCBNZXJnaW5nIERhdGE6DQpgYGBweXRob24NCmltcG9ydCBwYW5kYXMgYXMgcGQNCmZyb20gc2tsZWFybi5wcmVwcm9jZXNzaW5nIGltcG9ydCBTdGFuZGFyZFNjYWxlcg0KDQojIExvYWQgZGF0YXNldHMNCnRyYWluX2RmID0gcGQucmVhZF9jc3YoJy9tbnQvZGF0YS90cmFpbi5jc3YnKQ0KdW5pcXVlX2RmID0gcGQucmVhZF9jc3YoJy9tbnQvZGF0YS91bmlxdWVfbS5jc3YnKQ0KDQojIE1lcmdlIGRhdGFzZXRzDQptZXJnZWRfZGYgPSBwZC5jb25jYXQoW3RyYWluX2RmLCB1bmlxdWVfZGYuaWxvY1s6LCAxOl1dLCBheGlzPTEpDQoNCiMgRHJvcCBkdXBsaWNhdGUgdGFyZ2V0IGNvbHVtbnMgKGlmIGFwcGxpY2FibGUpDQppZiAnY3JpdGljYWxfdGVtcCcgaW4gbWVyZ2VkX2RmLmNvbHVtbnM6DQogICAgbWVyZ2VkX2RmID0gbWVyZ2VkX2RmLmxvY1s6LCB+bWVyZ2VkX2RmLmNvbHVtbnMuZHVwbGljYXRlZCgpXQ0KDQojIENoZWNrIGZvciBtaXNzaW5nIHZhbHVlcw0KcHJpbnQoIk1pc3NpbmcgdmFsdWVzOiIsIG1lcmdlZF9kZi5pc251bGwoKS5zdW0oKS5zdW0oKSkNCg0KIyBJbXB1dGUgbWlzc2luZyB2YWx1ZXMgKGlmIGFueSkNCm1lcmdlZF9kZi5maWxsbmEobWVyZ2VkX2RmLm1lYW4oKSwgaW5wbGFjZT1UcnVlKQ0KDQojIERlc2NyaWJlIGRhdGEgc2l6ZQ0KcHJpbnQoZiJEYXRhc2V0IGNvbnRhaW5zIHttZXJnZWRfZGYuc2hhcGVbMF19IHJvd3MgYW5kIHttZXJnZWRfZGYuc2hhcGVbMV19IGNvbHVtbnMuIikNCmBgYA0KDQojIyMjIGIpIEZlYXR1cmUgU2VsZWN0aW9uOg0KYGBgcHl0aG9uDQojIENhbGN1bGF0ZSBjb3JyZWxhdGlvbiBtYXRyaXggYW5kIGRyb3AgaGlnaGx5IGNvcnJlbGF0ZWQgZmVhdHVyZXMNCmNvcnJlbGF0aW9uX21hdHJpeCA9IG1lcmdlZF9kZi5jb3JyKCkNCmhpZ2hfY29ycl9mZWF0dXJlcyA9IGNvcnJlbGF0aW9uX21hdHJpeFtjb3JyZWxhdGlvbl9tYXRyaXggPiAwLjk1XS5zdGFjaygpLmluZGV4DQptZXJnZWRfZGYuZHJvcChjb2x1bW5zPVtjb2wgZm9yIGNvbCwgXyBpbiBoaWdoX2NvcnJfZmVhdHVyZXMgaWYgY29sICE9IF9dLCBpbnBsYWNlPVRydWUpDQoNCiMgU2NhbGUgZmVhdHVyZXMNCnNjYWxlciA9IFN0YW5kYXJkU2NhbGVyKCkNClggPSBzY2FsZXIuZml0X3RyYW5zZm9ybShtZXJnZWRfZGYuZHJvcChjb2x1bW5zPVsnY3JpdGljYWxfdGVtcCddKSkNCnkgPSBtZXJnZWRfZGZbJ2NyaXRpY2FsX3RlbXAnXQ0KYGBgDQoNCi0tLQ0KDQojIyMgKioyLiBTcGxpdHRpbmcgRGF0YSBhbmQgQ3Jvc3MtVmFsaWRhdGlvbioqDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQsIEtGb2xkDQoNCiMgVHJhaW4tdGVzdCBzcGxpdA0KWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KFgsIHksIHRlc3Rfc2l6ZT0wLjIsIHJhbmRvbV9zdGF0ZT00MikNCg0KIyA1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBzZXR1cA0Ka2YgPSBLRm9sZChuX3NwbGl0cz01LCBzaHVmZmxlPVRydWUsIHJhbmRvbV9zdGF0ZT00MikNCmBgYA0KDQotLS0NCg0KIyMjICoqMy4gTW9kZWwgVHJhaW5pbmcgYW5kIEh5cGVycGFyYW1ldGVyIFR1bmluZyoqDQojIyMjIGEpIERlZmluZSBFbGFzdGljIE5ldCBNb2RlbCBhbmQgSHlwZXJwYXJhbWV0ZXIgR3JpZDoNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgRWxhc3RpY05ldA0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgR3JpZFNlYXJjaENWDQoNCiMgRGVmaW5lIHBhcmFtZXRlciBncmlkDQpwYXJhbV9ncmlkID0gew0KICAgICdhbHBoYSc6IFswLjAwMSwgMC4wMSwgMC4xLCAxLCAxMF0sDQogICAgJ2wxX3JhdGlvJzogWzAuMSwgMC41LCAwLjcsIDAuOSwgMS4wXQ0KfQ0KDQojIEVsYXN0aWMgTmV0IHdpdGggR3JpZFNlYXJjaENWDQplbGFzdGljX25ldCA9IEdyaWRTZWFyY2hDVihFbGFzdGljTmV0KHJhbmRvbV9zdGF0ZT00MiksIHBhcmFtX2dyaWQsIGN2PWtmLCBzY29yaW5nPSduZWdfbWVhbl9hYnNvbHV0ZV9lcnJvcicpDQplbGFzdGljX25ldC5maXQoWF90cmFpbiwgeV90cmFpbikNCg0KIyBCZXN0IGh5cGVycGFyYW1ldGVycw0KcHJpbnQoIkJlc3QgSHlwZXJwYXJhbWV0ZXJzOiIsIGVsYXN0aWNfbmV0LmJlc3RfcGFyYW1zXykNCmBgYA0KDQotLS0NCg0KIyMjICoqNC4gTW9kZWwgRXZhbHVhdGlvbioqDQojIyMjIGEpIE1ldHJpY3MgYW5kIFZpc3VhbGl6YXRpb25zOg0KYGBgcHl0aG9uDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgbWVhbl9hYnNvbHV0ZV9lcnJvciwgbWVhbl9zcXVhcmVkX2Vycm9yLCByMl9zY29yZQ0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQojIFByZWRpY3Rpb25zDQp5X3ByZWQgPSBlbGFzdGljX25ldC5wcmVkaWN0KFhfdGVzdCkNCg0KIyBNZXRyaWNzDQptYWUgPSBtZWFuX2Fic29sdXRlX2Vycm9yKHlfdGVzdCwgeV9wcmVkKQ0KbXNlID0gbWVhbl9zcXVhcmVkX2Vycm9yKHlfdGVzdCwgeV9wcmVkKQ0KcjIgPSByMl9zY29yZSh5X3Rlc3QsIHlfcHJlZCkNCg0KcHJpbnQoZiJNZWFuIEFic29sdXRlIEVycm9yOiB7bWFlOi40Zn0iKQ0KcHJpbnQoZiJNZWFuIFNxdWFyZWQgRXJyb3I6IHttc2U6LjRmfSIpDQpwcmludChmIlJeMiBTY29yZToge3IyOi40Zn0iKQ0KDQojIFZpc3VhbGl6YXRpb25zDQpwbHQuZmlndXJlKGZpZ3NpemU9KDEyLCA2KSkNCg0KIyBBY3R1YWwgdnMgUHJlZGljdGVkDQpwbHQuc3VicGxvdCgxLCAyLCAxKQ0KcGx0LnNjYXR0ZXIoeV90ZXN0LCB5X3ByZWQsIGFscGhhPTAuNikNCnBsdC5wbG90KFt5Lm1pbigpLCB5Lm1heCgpXSwgW3kubWluKCksIHkubWF4KCldLCAnci0tJykNCnBsdC50aXRsZSgiQWN0dWFsIHZzIFByZWRpY3RlZCIpDQpwbHQueGxhYmVsKCJBY3R1YWwiKQ0KcGx0LnlsYWJlbCgiUHJlZGljdGVkIikNCg0KIyBSZXNpZHVhbHMNCnJlc2lkdWFscyA9IHlfdGVzdCAtIHlfcHJlZA0KcGx0LnN1YnBsb3QoMSwgMiwgMikNCnBsdC5zY2F0dGVyKHlfcHJlZCwgcmVzaWR1YWxzLCBhbHBoYT0wLjYpDQpwbHQuYXhobGluZSgwLCBjb2xvcj0ncicsIGxpbmVzdHlsZT0nLS0nKQ0KcGx0LnRpdGxlKCJSZXNpZHVhbHMgdnMgUHJlZGljdGVkIikNCnBsdC54bGFiZWwoIlByZWRpY3RlZCIpDQpwbHQueWxhYmVsKCJSZXNpZHVhbHMiKQ0KDQpwbHQudGlnaHRfbGF5b3V0KCkNCnBsdC5zaG93KCkNCmBgYA0KDQotLS0NCg0KIyMjICoqNS4gRmVhdHVyZSBJbXBvcnRhbmNlKioNCmBgYHB5dGhvbg0KIyBGZWF0dXJlIGltcG9ydGFuY2UgZnJvbSBFbGFzdGljIE5ldA0KaW1wb3J0IG51bXB5IGFzIG5wDQoNCmltcG9ydGFuY2UgPSBucC5hYnMoZWxhc3RpY19uZXQuYmVzdF9lc3RpbWF0b3JfLmNvZWZfKQ0Kc29ydGVkX2lkeCA9IG5wLmFyZ3NvcnQoaW1wb3J0YW5jZSlbOjotMV0NCnRvcF9mZWF0dXJlcyA9IG5wLmFycmF5KG1lcmdlZF9kZi5jb2x1bW5zWzotMV0pW3NvcnRlZF9pZHhbOjIwXV0gICMgVG9wIDIwIGZlYXR1cmVzDQoNCiMgQmFyIHBsb3QNCnBsdC5maWd1cmUoZmlnc2l6ZT0oMTAsIDYpKQ0KcGx0LmJhcmgodG9wX2ZlYXR1cmVzLCBpbXBvcnRhbmNlW3NvcnRlZF9pZHhbOjIwXV0pDQpwbHQuZ2NhKCkuaW52ZXJ0X3lheGlzKCkNCnBsdC50aXRsZSgiVG9wIDIwIEZlYXR1cmUgSW1wb3J0YW5jZXMgKEVsYXN0aWMgTmV0KSIpDQpwbHQueGxhYmVsKCJDb2VmZmljaWVudCBNYWduaXR1ZGUiKQ0KcGx0LnNob3coKQ0KYGBgDQoNCi0tLQ0KDQojIyMgKio2LiBSZWd1bGFyaXphdGlvbiBTdHJlbmd0aCB2cyBNQUUqKg0KYGBgcHl0aG9uDQojIEFuYWx5emUgZWZmZWN0IG9mIGFscGhhIG9uIE1BRQ0KcmVzdWx0cyA9IGVsYXN0aWNfbmV0LmN2X3Jlc3VsdHNfDQphbHBoYV92YWx1ZXMgPSBbcGFyYW1zWydhbHBoYSddIGZvciBwYXJhbXMgaW4gcmVzdWx0c1sncGFyYW1zJ11dDQptYWVfdmFsdWVzID0gLXJlc3VsdHNbJ21lYW5fdGVzdF9zY29yZSddDQoNCnBsdC5maWd1cmUoZmlnc2l6ZT0oOCwgNSkpDQpwbHQucGxvdChhbHBoYV92YWx1ZXMsIG1hZV92YWx1ZXMsIG1hcmtlcj0nbycpDQpwbHQueHNjYWxlKCdsb2cnKQ0KcGx0LnhsYWJlbCgiQWxwaGEgKFJlZ3VsYXJpemF0aW9uIFN0cmVuZ3RoKSIpDQpwbHQueWxhYmVsKCJOZWdhdGl2ZSBNZWFuIEFic29sdXRlIEVycm9yIikNCnBsdC50aXRsZSgiRWZmZWN0IG9mIEFscGhhIG9uIE1BRSIpDQpwbHQuc2hvdygpDQpgYGANCg0KLS0tDQoNCiMjIyBHUFUgVXRpbGl6YXRpb24NCkZvciBhZGRpdGlvbmFsIGFjY2VsZXJhdGlvbiB1c2luZyBHUFUgKGlmIGF2YWlsYWJsZSksIHdlIGNhbiBpbXBsZW1lbnQgdGhpcyB1c2luZyBQeVRvcmNoIA0K