Class 1 transcript summarized and broken down into sections going
into case studfy 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.
- 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\)
- 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.
- 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.
- 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.
- 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:
- Loading and merging datasets.
- Data preprocessing:
- Identifying duplicate columns (target).
- Handling missing values (if any).
- Dropping unnecessary features.
- Training two models:
- Linear regression with L1 regularization (Lasso).
- Linear regression with L2 regularization (Ridge).
- 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
- Metrics: MAE, MSE, and \(R^2\) scores for both Lasso and Ridge
models.
- Visualizations:
- Scatter plots showing actual vs predicted values for both
models.
- 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:
- 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.
- 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.
- Feature Importance:
- The magnitudes of the coefficients (absolute values) are plotted to
visualize the top 10 most influential features for both models.
- 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:
- Optimal Alpha Values:
- Printed best
alpha
for both Lasso and Ridge from
GridSearchCV
.
- Model Metrics:
- MAE, MSE, and \(R^2\) scores after
tuning.
- Feature Importance:
- Bar plots showing the top 10 features for each model.
- 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