Logistic Regression Study Guide

Key Concepts

1. Categorical Data

  • Definition: Data based on classes (e.g., red/green/blue, true/false).
  • Transformation: Use one-hot encoding to convert non-numeric data into numeric format.
    • Example: Red = [1, 0, 0], Green = [0, 1, 0], Blue = [0, 0, 1].
    • Each category is represented by a binary vector.
  • Discrete Data: Numeric values without fractional parts (e.g., number of rooms, children).
    • Do not one-hot encode discrete data; treat it as numeric.

2. Linear to Logistic Regression

  • Linear regression predicts continuous values, but logistic regression is used for categorical targets.
  • Logistic regression transforms linear outputs into probabilities using the sigmoid function.

3. The Sigmoid Function

  • Formula: \(\sigma(x) = \frac{1}{1 + e^{-x}}\)
  • Squeezes input values into the range (0, 1), representing probabilities.
  • Default classification threshold is 0.5 but can be adjusted based on the use case (e.g., fraud detection).

4. Log Loss (Logarithmic Loss)

  • Measures the distance between predicted probabilities and actual binary targets.
  • Formula:
    \(\text{Log Loss} = -\frac{1}{N} \sum_{i=1}^N \left[ y_i \log(p_i) + (1-y_i) \log(1-p_i) \right]\)
    • \(y_i\): Actual class (0 or 1).
    • \(p_i\): Predicted probability for class 1.
  • Penalizes predictions farther from the actual target.

5. Minimizing Log Loss

  • Uses gradient descent to adjust weights (\(m\)) and minimize loss.
  • Partial derivatives of log loss guide weight updates: \(m_{new} = m_{old} - \alpha \frac{\partial J}{\partial m}\), where \(J\) is the log loss and \(\alpha\) is the learning rate.

6. Multiclass Classification

  • Extends binary logistic regression to multiple categories.
  • Two approaches:
    • One-vs-All (OvA): Train a separate classifier for each class.
    • One-vs-One (OvO): Compare every pair of classes; assign the class with the most wins.
  • Use libraries (e.g., sklearn) for built-in multiclass implementation.

Demonstration of Logistic Regression

Part I: Binary Classification (Breast Cancer Dataset)

Steps:

  1. Data Preparation:
    • Import dataset from sklearn.datasets.
    • Convert data to a DataFrame and add column names.
    • Separate features (X) and targets (y).
  2. Logistic Regression:
    • Use LogisticRegressionCV for cross-validation.
    • Fit the model: model.fit(X, y).
    • Retrieve model coefficients with model.coef_.
  3. Cross-Validation:
    • For unbiased performance metrics, use cross_val_score.

    • Example:

      from sklearn.model_selection import cross_val_score
      scores = cross_val_score(model, X, y, scoring='accuracy', cv=5)
      print("Accuracy:", scores.mean())

Part II: Cross-Validation and Regularization

  • Use LogisticRegressionCV to optimize regularization parameters.
  • Interpret the results and evaluate metrics.

Case Study: Hospital Readmission Prediction

Problem Overview

  • Predict patient readmission within 30 days using logistic regression.
  • Challenges:
    • Missing data must be imputed.
    • Ethical considerations in using sensitive features (e.g., race).
    • Imbalanced classes.

Assignment Steps:

  1. Data Preprocessing:
    • Handle missing data using imputation techniques (e.g., mean, median).
    • Normalize/scale features for better model performance.
  2. Model Development:
    • Build a logistic regression model for each target class (multiclass setup).
    • Use cross-validation to evaluate performance.
  3. Feature Importance:
    • Analyze the top 5 important features contributing to predictions using model coefficients.

Mathematical and Coding Representations

Mathematical Representation

  1. Sigmoid Function:
    \(\sigma(x) = \frac{1}{1 + e^{-x}}\)
  2. Log Loss:
    \(J = -\frac{1}{N} \sum_{i=1}^N \left[ y_i \log(p_i) + (1-y_i) \log(1-p_i) \right]\)
  3. Gradient Update:
    \(m_{new} = m_{old} - \alpha \frac{\partial J}{\partial m}\)

Python Code Representation

  1. One-Hot Encoding:

    import pandas as pd
    from sklearn.preprocessing import OneHotEncoder
    
    data = {'Color': ['Red', 'Blue', 'Green']}
    df = pd.DataFrame(data)
    encoder = OneHotEncoder()
    encoded = encoder.fit_transform(df[['Color']]).toarray()
    print(encoded)
  2. Logistic Regression:

    from sklearn.datasets import load_breast_cancer
    from sklearn.model_selection import train_test_split
    from sklearn.linear_model import LogisticRegressionCV
    
    # Load data
    data = load_breast_cancer()
    X, y = data.data, data.target
    
    # Split data
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
    
    # Logistic Regression with CV
    model = LogisticRegressionCV(cv=5, max_iter=1000).fit(X_train, y_train)
    print("Accuracy:", model.score(X_test, y_test))
  3. Cross-Validation:

    from sklearn.model_selection import cross_val_score
    
    scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
    print("Cross-Validation Accuracy:", scores.mean())

Visualization of Sigmoid Function

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-10, 10, 100)
sigmoid = 1 / (1 + np.exp(-x))

plt.plot(x, sigmoid)
plt.title('Sigmoid Function')
plt.xlabel('x')
plt.ylabel('Sigmoid(x)')
plt.grid()
plt.show()

Study Guide: Logistic Regression with Mathematical and Coding Representations


1. Categorical Data

  • Definition: Data representing classes or categories (e.g., red, green, blue; true/false).
  • Challenges: Categorical data must be transformed into numerical data to be used in most machine learning models.
  • Transformation:
    • One-Hot Encoding: Converts categorical features into binary columns, where each unique value becomes a column. E.g., for colors red, green, blue:
      • Red: [1, 0, 0]
      • Green: [0, 1, 0]
      • Blue: [0, 0, 1]

Python Example:

import pandas as pd

data = {'Color': ['Red', 'Green', 'Blue']}
df = pd.DataFrame(data)
df_encoded = pd.get_dummies(df, columns=['Color'])

Mathematical Representation: If \(C\) represents the categories and \(x\) is the input, the transformation is: \[ \text{One-hot encoded vector: } \mathbf{x}_{\text{encoded}} = [x_1, x_2, \ldots, x_n], \text{where } x_i = 1 \text{ if } x = C_i, \text{ else } 0. \]


2. Linear to Logistic Regression

  • Linear regression predicts continuous values. Logistic regression transforms this to predict probabilities for binary outcomes.
  • Key Equation: \[ z = \mathbf{w}^T\mathbf{x} + b, \quad p = \sigma(z), \quad \sigma(z) = \frac{1}{1 + e^{-z}} \] Where \(\sigma(z)\) (sigmoid function) squashes \(z\) to range [0, 1].

Python Example:

from sklearn.linear_model import LogisticRegression
model = LogisticRegression()
model.fit(X_train, y_train)

3. The Sigmoid Function

  • Equation: \(\sigma(z) = \frac{1}{1 + e^{-z}}\)
  • Properties:
    • \(z \to +\infty\): \(\sigma(z) \to 1\)
    • \(z \to -\infty\): \(\sigma(z) \to 0\)
    • Outputs probabilities.

Python Example:

import numpy as np

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

z = np.linspace(-10, 10, 100)
sigmoid_values = sigmoid(z)

4. Log Loss

  • Measures the distance between predicted probabilities and actual class labels.
  • Equation: \[ \text{Log Loss: } L(y, \hat{y}) = -\frac{1}{n} \sum_{i=1}^{n} \left[ y_i \log(\hat{y}_i) + (1 - y_i) \log(1 - \hat{y}_i) \right] \] Where:
    • \(y_i\): Actual label.
    • \(\hat{y}_i\): Predicted probability.

Python Example:

from sklearn.metrics import log_loss
loss = log_loss(y_true, y_pred)

5. Minimizing Log Loss

  • Achieved through optimization (e.g., gradient descent).
  • Gradient Descent:
    • Update rule: \(w = w - \alpha \nabla L(w)\), where \(\alpha\) is the learning rate.
  • Python Example:
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(max_iter=100)
model.fit(X_train, y_train)

6. Multiclass Classification

  • Extends binary logistic regression to multiple classes.
  • Approaches:
    • One-vs-Rest (OvR): Train separate classifiers for each class.
    • Softmax Regression: Directly models all classes using probabilities.
  • Softmax Function: \[ \sigma(z_j) = \frac{e^{z_j}}{\sum_{k=1}^K e^{z_k}} \]

Python Example:

from sklearn.linear_model import LogisticRegression
model = LogisticRegression(multi_class='multinomial', solver='lbfgs')
model.fit(X_train, y_train)

Case Study: Predicting Diabetes Readmission

Objective

  • Predict hospital readmission within 30 days using logistic regression.
  • Handle missing data via imputation.

Steps:

  1. Data Preprocessing:
    • Handle missing values (e.g., mean/mode imputation).
    • Encode categorical variables (e.g., one-hot encoding).
  2. Model Building:
    • Train logistic regression for three classes of readmission:
      • No readmission.
      • Readmission in less than 30 days.
      • Readmission in more than 30 days.
    • Evaluate using metrics like log loss or accuracy.
  3. Feature Importance:
    • Extract coefficients to identify top 5 predictive features.

Python Code:

from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

# Data preprocessing
imputer = SimpleImputer(strategy='mean')
X = imputer.fit_transform(X)

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

# Logistic regression model
model = LogisticRegression(multi_class='multinomial', solver='lbfgs', max_iter=500)
model.fit(X_train, y_train)

# Predictions
y_pred = model.predict(X_test)

# Feature importance
importance = model.coef_
print("Top 5 Features:", importance.argsort()[-5:])

# Accuracy
accuracy = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {accuracy}")

Variable Importance Interpretation

  • Coefficients reflect the importance of each feature.
  • Analyze top variables to derive insights for readmission patterns.

Assignment

  • Train and evaluate logistic regression on the diabetes dataset.
  • Report top 5 features and their significance.
  • Submit results with a clear explanation of findings.

Deliverable: - Code file (e.g., FirstName_LastName_LogReg_Assignment.py). - Written report on model performance and feature importance.

Key Takeaways

  1. Logistic Regression Bridges Linear Models and Probabilistic Predictions:

    • Logistic regression extends linear regression for categorical target variables by employing the sigmoid function to output probabilities. This allows for binary or multiclass predictions by interpreting probabilities as class memberships.
  2. Log Loss as a Metric:

    • Unlike linear regression’s mean squared error, logistic regression uses log loss to measure the model’s performance. It penalizes predictions further from the true class, ensuring probabilities are accurate.
  3. Handling Multiclass Problems:

    • Multiclass classification can be addressed using methods like one-vs-rest (OvR) or softmax regression. While OvR is computationally efficient for a few classes, it scales poorly with many classes due to class imbalance issues.
  4. Importance of Sigmoid Function in Logistic Regression: The sigmoid function transforms the linear regression output into probabilities, making it possible to handle binary classification tasks effectively. This function ensures that predictions fall within the range [0, 1], which can then be used to classify data into distinct categories.

  5. Log Loss for Classification: Logarithmic loss (log loss) quantifies the error in probabilistic predictions by penalizing predictions that deviate significantly from the true labels. It forms a convex function with a well-defined minimum, facilitating optimization and convergence.

  6. Multiclass Classification Challenges: Logistic regression can be extended to multiclass problems through techniques like One-vs-All and One-vs-One. While these methods allow logistic regression to handle more than two classes, they come with scalability and bias challenges as the number of classes increases.


Questions for Class Discussion

  1. Threshold Adjustment in Logistic Regression:

    • How can adjusting the classification threshold (e.g., moving it from 0.5 to 0.2 for fraud detection) impact the balance between false positives and false negatives? What real-world examples highlight the importance of this?
  2. Log Loss vs. Accuracy:

    • In what scenarios might log loss be a more appropriate evaluation metric than accuracy? Can you provide examples where accuracy might be misleading?
  3. Ethical Considerations in Categorical Variables:

    • When including sensitive variables like race or gender in logistic regression, how can we ensure the model is ethically sound and avoids discriminatory practices while leveraging potentially predictive information?
  4. On Sigmoid Threshold Adjustment: In real-world applications like fraud detection, how do we decide on the optimal threshold for classification beyond the default value of 0.5? What strategies or metrics should guide this decision?

  5. On Handling Missing Data: When dealing with missing values in a dataset, as mentioned in the diabetes case study, what factors should influence the choice of an imputation strategy? How do we ensure the imputation does not introduce bias into the model?

  6. On Ethical Considerations in Feature Selection: In cases where sensitive features like race are included in the dataset, how do we balance the potential utility of such features with ethical concerns and the risk of perpetuating bias in predictions?

Best Takeaways

  1. Logistic Regression Bridges Linear Models and Probabilistic Predictions:
    • Logistic regression extends linear regression for categorical target variables by employing the sigmoid function to output probabilities. This allows for binary or multiclass predictions by interpreting probabilities as class memberships.
  2. Log Loss for Classification:
    • Logarithmic loss (log loss) quantifies the error in probabilistic predictions by penalizing predictions that deviate significantly from the true labels. It forms a convex function with a well-defined minimum, facilitating optimization and convergence.

Best Questions for Class Discussion

  1. Threshold Adjustment in Logistic Regression:
    • How can adjusting the classification threshold (e.g., moving it from 0.5 to 0.2 for fraud detection) impact the balance between false positives and false negatives? What real-world examples highlight the importance of this?
  2. Ethical Considerations in Categorical Variables:
    • When including sensitive variables like race or gender in logistic regression, how can we ensure the model is ethically sound and avoids discriminatory practices while leveraging potentially predictive information?
LS0tDQp0aXRsZTogIjczMzMzIE1vZHVsZSAzIFRyYXNuY3JpcHQgYW5kIFN1bW1hcmllcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgTG9naXN0aWMgUmVncmVzc2lvbiBTdHVkeSBHdWlkZQ0KDQojIyBLZXkgQ29uY2VwdHMNCg0KIyMjIDEuIENhdGVnb3JpY2FsIERhdGENCi0gKipEZWZpbml0aW9uKio6IERhdGEgYmFzZWQgb24gY2xhc3NlcyAoZS5nLiwgcmVkL2dyZWVuL2JsdWUsIHRydWUvZmFsc2UpLg0KLSAqKlRyYW5zZm9ybWF0aW9uKio6IFVzZSAqKm9uZS1ob3QgZW5jb2RpbmcqKiB0byBjb252ZXJ0IG5vbi1udW1lcmljIGRhdGEgaW50byBudW1lcmljIGZvcm1hdC4NCiAgLSBFeGFtcGxlOiBSZWQgPSBbMSwgMCwgMF0sIEdyZWVuID0gWzAsIDEsIDBdLCBCbHVlID0gWzAsIDAsIDFdLg0KICAtIEVhY2ggY2F0ZWdvcnkgaXMgcmVwcmVzZW50ZWQgYnkgYSBiaW5hcnkgdmVjdG9yLg0KLSAqKkRpc2NyZXRlIERhdGEqKjogTnVtZXJpYyB2YWx1ZXMgd2l0aG91dCBmcmFjdGlvbmFsIHBhcnRzIChlLmcuLCBudW1iZXIgb2Ygcm9vbXMsIGNoaWxkcmVuKS4NCiAgLSAqKkRvIG5vdCBvbmUtaG90IGVuY29kZSBkaXNjcmV0ZSBkYXRhKio7IHRyZWF0IGl0IGFzIG51bWVyaWMuDQoNCiMjIyAyLiBMaW5lYXIgdG8gTG9naXN0aWMgUmVncmVzc2lvbg0KLSBMaW5lYXIgcmVncmVzc2lvbiBwcmVkaWN0cyBjb250aW51b3VzIHZhbHVlcywgYnV0IGxvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgdXNlZCBmb3IgY2F0ZWdvcmljYWwgdGFyZ2V0cy4NCi0gTG9naXN0aWMgcmVncmVzc2lvbiB0cmFuc2Zvcm1zIGxpbmVhciBvdXRwdXRzIGludG8gcHJvYmFiaWxpdGllcyB1c2luZyB0aGUgKipzaWdtb2lkIGZ1bmN0aW9uKiouDQoNCiMjIyAzLiBUaGUgU2lnbW9pZCBGdW5jdGlvbg0KLSAqKkZvcm11bGEqKjogXCggXHNpZ21hKHgpID0gXGZyYWN7MX17MSArIGVeey14fX0gXCkNCi0gU3F1ZWV6ZXMgaW5wdXQgdmFsdWVzIGludG8gdGhlIHJhbmdlICgwLCAxKSwgcmVwcmVzZW50aW5nIHByb2JhYmlsaXRpZXMuDQotIERlZmF1bHQgY2xhc3NpZmljYXRpb24gdGhyZXNob2xkIGlzIDAuNSBidXQgY2FuIGJlIGFkanVzdGVkIGJhc2VkIG9uIHRoZSB1c2UgY2FzZSAoZS5nLiwgZnJhdWQgZGV0ZWN0aW9uKS4NCg0KIyMjIDQuIExvZyBMb3NzIChMb2dhcml0aG1pYyBMb3NzKQ0KLSBNZWFzdXJlcyB0aGUgZGlzdGFuY2UgYmV0d2VlbiBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBhbmQgYWN0dWFsIGJpbmFyeSB0YXJnZXRzLg0KLSAqKkZvcm11bGEqKjogIA0KICBcKCBcdGV4dHtMb2cgTG9zc30gPSAtXGZyYWN7MX17Tn0gXHN1bV97aT0xfV5OIFxsZWZ0WyB5X2kgXGxvZyhwX2kpICsgKDEteV9pKSBcbG9nKDEtcF9pKSBccmlnaHRdIFwpICANCiAgLSBcKCB5X2kgXCk6IEFjdHVhbCBjbGFzcyAoMCBvciAxKS4NCiAgLSBcKCBwX2kgXCk6IFByZWRpY3RlZCBwcm9iYWJpbGl0eSBmb3IgY2xhc3MgMS4NCi0gUGVuYWxpemVzIHByZWRpY3Rpb25zIGZhcnRoZXIgZnJvbSB0aGUgYWN0dWFsIHRhcmdldC4NCg0KIyMjIDUuIE1pbmltaXppbmcgTG9nIExvc3MNCi0gVXNlcyBncmFkaWVudCBkZXNjZW50IHRvIGFkanVzdCB3ZWlnaHRzIChcKG1cKSkgYW5kIG1pbmltaXplIGxvc3MuDQotIFBhcnRpYWwgZGVyaXZhdGl2ZXMgb2YgbG9nIGxvc3MgZ3VpZGUgd2VpZ2h0IHVwZGF0ZXM6DQogIFwoIG1fe25ld30gPSBtX3tvbGR9IC0gXGFscGhhIFxmcmFje1xwYXJ0aWFsIEp9e1xwYXJ0aWFsIG19IFwpLA0KICB3aGVyZSBcKCBKIFwpIGlzIHRoZSBsb2cgbG9zcyBhbmQgXCggXGFscGhhIFwpIGlzIHRoZSBsZWFybmluZyByYXRlLg0KDQojIyMgNi4gTXVsdGljbGFzcyBDbGFzc2lmaWNhdGlvbg0KLSBFeHRlbmRzIGJpbmFyeSBsb2dpc3RpYyByZWdyZXNzaW9uIHRvIG11bHRpcGxlIGNhdGVnb3JpZXMuDQotIFR3byBhcHByb2FjaGVzOg0KICAtICoqT25lLXZzLUFsbCAoT3ZBKSoqOiBUcmFpbiBhIHNlcGFyYXRlIGNsYXNzaWZpZXIgZm9yIGVhY2ggY2xhc3MuDQogIC0gKipPbmUtdnMtT25lIChPdk8pKio6IENvbXBhcmUgZXZlcnkgcGFpciBvZiBjbGFzc2VzOyBhc3NpZ24gdGhlIGNsYXNzIHdpdGggdGhlIG1vc3Qgd2lucy4NCi0gVXNlIGxpYnJhcmllcyAoZS5nLiwgYHNrbGVhcm5gKSBmb3IgYnVpbHQtaW4gbXVsdGljbGFzcyBpbXBsZW1lbnRhdGlvbi4NCg0KLS0tDQoNCiMjIERlbW9uc3RyYXRpb24gb2YgTG9naXN0aWMgUmVncmVzc2lvbg0KDQojIyMgUGFydCBJOiBCaW5hcnkgQ2xhc3NpZmljYXRpb24gKEJyZWFzdCBDYW5jZXIgRGF0YXNldCkNCiMjIyMgU3RlcHM6DQoxLiAqKkRhdGEgUHJlcGFyYXRpb24qKjoNCiAgIC0gSW1wb3J0IGRhdGFzZXQgZnJvbSBgc2tsZWFybi5kYXRhc2V0c2AuDQogICAtIENvbnZlcnQgZGF0YSB0byBhIERhdGFGcmFtZSBhbmQgYWRkIGNvbHVtbiBuYW1lcy4NCiAgIC0gU2VwYXJhdGUgZmVhdHVyZXMgKFgpIGFuZCB0YXJnZXRzICh5KS4NCjIuICoqTG9naXN0aWMgUmVncmVzc2lvbioqOg0KICAgLSBVc2UgYExvZ2lzdGljUmVncmVzc2lvbkNWYCBmb3IgY3Jvc3MtdmFsaWRhdGlvbi4NCiAgIC0gRml0IHRoZSBtb2RlbDogYG1vZGVsLmZpdChYLCB5KWAuDQogICAtIFJldHJpZXZlIG1vZGVsIGNvZWZmaWNpZW50cyB3aXRoIGBtb2RlbC5jb2VmX2AuDQoNCjMuICoqQ3Jvc3MtVmFsaWRhdGlvbioqOg0KICAgLSBGb3IgdW5iaWFzZWQgcGVyZm9ybWFuY2UgbWV0cmljcywgdXNlIGBjcm9zc192YWxfc2NvcmVgLg0KICAgLSBFeGFtcGxlOiAgDQogICAgIGBgYHB5dGhvbg0KICAgICBmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCBjcm9zc192YWxfc2NvcmUNCiAgICAgc2NvcmVzID0gY3Jvc3NfdmFsX3Njb3JlKG1vZGVsLCBYLCB5LCBzY29yaW5nPSdhY2N1cmFjeScsIGN2PTUpDQogICAgIHByaW50KCJBY2N1cmFjeToiLCBzY29yZXMubWVhbigpKQ0KICAgICBgYGANCg0KIyMjIFBhcnQgSUk6IENyb3NzLVZhbGlkYXRpb24gYW5kIFJlZ3VsYXJpemF0aW9uDQotIFVzZSBgTG9naXN0aWNSZWdyZXNzaW9uQ1ZgIHRvIG9wdGltaXplIHJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlcnMuDQotIEludGVycHJldCB0aGUgcmVzdWx0cyBhbmQgZXZhbHVhdGUgbWV0cmljcy4NCg0KLS0tDQoNCiMjIENhc2UgU3R1ZHk6IEhvc3BpdGFsIFJlYWRtaXNzaW9uIFByZWRpY3Rpb24NCg0KIyMjIFByb2JsZW0gT3ZlcnZpZXcNCi0gUHJlZGljdCBwYXRpZW50IHJlYWRtaXNzaW9uIHdpdGhpbiAzMCBkYXlzIHVzaW5nIGxvZ2lzdGljIHJlZ3Jlc3Npb24uDQotICoqQ2hhbGxlbmdlcyoqOg0KICAtIE1pc3NpbmcgZGF0YSBtdXN0IGJlIGltcHV0ZWQuDQogIC0gRXRoaWNhbCBjb25zaWRlcmF0aW9ucyBpbiB1c2luZyBzZW5zaXRpdmUgZmVhdHVyZXMgKGUuZy4sIHJhY2UpLg0KICAtIEltYmFsYW5jZWQgY2xhc3Nlcy4NCg0KIyMjIEFzc2lnbm1lbnQgU3RlcHM6DQoxLiAqKkRhdGEgUHJlcHJvY2Vzc2luZyoqOg0KICAgLSBIYW5kbGUgbWlzc2luZyBkYXRhIHVzaW5nIGltcHV0YXRpb24gdGVjaG5pcXVlcyAoZS5nLiwgbWVhbiwgbWVkaWFuKS4NCiAgIC0gTm9ybWFsaXplL3NjYWxlIGZlYXR1cmVzIGZvciBiZXR0ZXIgbW9kZWwgcGVyZm9ybWFuY2UuDQoyLiAqKk1vZGVsIERldmVsb3BtZW50Kio6DQogICAtIEJ1aWxkIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBmb3IgZWFjaCB0YXJnZXQgY2xhc3MgKG11bHRpY2xhc3Mgc2V0dXApLg0KICAgLSBVc2UgY3Jvc3MtdmFsaWRhdGlvbiB0byBldmFsdWF0ZSBwZXJmb3JtYW5jZS4NCjMuICoqRmVhdHVyZSBJbXBvcnRhbmNlKio6DQogICAtIEFuYWx5emUgdGhlIHRvcCA1IGltcG9ydGFudCBmZWF0dXJlcyBjb250cmlidXRpbmcgdG8gcHJlZGljdGlvbnMgdXNpbmcgbW9kZWwgY29lZmZpY2llbnRzLg0KDQotLS0NCg0KIyMgTWF0aGVtYXRpY2FsIGFuZCBDb2RpbmcgUmVwcmVzZW50YXRpb25zDQoNCiMjIyBNYXRoZW1hdGljYWwgUmVwcmVzZW50YXRpb24NCjEuICoqU2lnbW9pZCBGdW5jdGlvbioqOiAgDQogICBcKCBcc2lnbWEoeCkgPSBcZnJhY3sxfXsxICsgZV57LXh9fSBcKQ0KMi4gKipMb2cgTG9zcyoqOiAgDQogICBcKCBKID0gLVxmcmFjezF9e059IFxzdW1fe2k9MX1eTiBcbGVmdFsgeV9pIFxsb2cocF9pKSArICgxLXlfaSkgXGxvZygxLXBfaSkgXHJpZ2h0XSBcKQ0KMy4gKipHcmFkaWVudCBVcGRhdGUqKjogIA0KICAgXCggbV97bmV3fSA9IG1fe29sZH0gLSBcYWxwaGEgXGZyYWN7XHBhcnRpYWwgSn17XHBhcnRpYWwgbX0gXCkNCg0KIyMjIFB5dGhvbiBDb2RlIFJlcHJlc2VudGF0aW9uDQoxLiAqKk9uZS1Ib3QgRW5jb2RpbmcqKjoNCiAgIGBgYHB5dGhvbg0KICAgaW1wb3J0IHBhbmRhcyBhcyBwZA0KICAgZnJvbSBza2xlYXJuLnByZXByb2Nlc3NpbmcgaW1wb3J0IE9uZUhvdEVuY29kZXINCiAgIA0KICAgZGF0YSA9IHsnQ29sb3InOiBbJ1JlZCcsICdCbHVlJywgJ0dyZWVuJ119DQogICBkZiA9IHBkLkRhdGFGcmFtZShkYXRhKQ0KICAgZW5jb2RlciA9IE9uZUhvdEVuY29kZXIoKQ0KICAgZW5jb2RlZCA9IGVuY29kZXIuZml0X3RyYW5zZm9ybShkZltbJ0NvbG9yJ11dKS50b2FycmF5KCkNCiAgIHByaW50KGVuY29kZWQpDQogICBgYGANCjIuICoqTG9naXN0aWMgUmVncmVzc2lvbioqOg0KICAgYGBgcHl0aG9uDQogICBmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IGxvYWRfYnJlYXN0X2NhbmNlcg0KICAgZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgdHJhaW5fdGVzdF9zcGxpdA0KICAgZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgTG9naXN0aWNSZWdyZXNzaW9uQ1YNCiAgIA0KICAgIyBMb2FkIGRhdGENCiAgIGRhdGEgPSBsb2FkX2JyZWFzdF9jYW5jZXIoKQ0KICAgWCwgeSA9IGRhdGEuZGF0YSwgZGF0YS50YXJnZXQNCiAgIA0KICAgIyBTcGxpdCBkYXRhDQogICBYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoWCwgeSwgdGVzdF9zaXplPTAuMiwgcmFuZG9tX3N0YXRlPTQyKQ0KICAgDQogICAjIExvZ2lzdGljIFJlZ3Jlc3Npb24gd2l0aCBDVg0KICAgbW9kZWwgPSBMb2dpc3RpY1JlZ3Jlc3Npb25DVihjdj01LCBtYXhfaXRlcj0xMDAwKS5maXQoWF90cmFpbiwgeV90cmFpbikNCiAgIHByaW50KCJBY2N1cmFjeToiLCBtb2RlbC5zY29yZShYX3Rlc3QsIHlfdGVzdCkpDQogICBgYGANCg0KMy4gKipDcm9zcy1WYWxpZGF0aW9uKio6DQogICBgYGBweXRob24NCiAgIGZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IGNyb3NzX3ZhbF9zY29yZQ0KICAgDQogICBzY29yZXMgPSBjcm9zc192YWxfc2NvcmUobW9kZWwsIFgsIHksIGN2PTUsIHNjb3Jpbmc9J2FjY3VyYWN5JykNCiAgIHByaW50KCJDcm9zcy1WYWxpZGF0aW9uIEFjY3VyYWN5OiIsIHNjb3Jlcy5tZWFuKCkpDQogICBgYGANCg0KIyMjIFZpc3VhbGl6YXRpb24gb2YgU2lnbW9pZCBGdW5jdGlvbg0KYGBgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCg0KeCA9IG5wLmxpbnNwYWNlKC0xMCwgMTAsIDEwMCkNCnNpZ21vaWQgPSAxIC8gKDEgKyBucC5leHAoLXgpKQ0KDQpwbHQucGxvdCh4LCBzaWdtb2lkKQ0KcGx0LnRpdGxlKCdTaWdtb2lkIEZ1bmN0aW9uJykNCnBsdC54bGFiZWwoJ3gnKQ0KcGx0LnlsYWJlbCgnU2lnbW9pZCh4KScpDQpwbHQuZ3JpZCgpDQpwbHQuc2hvdygpDQpgYGANCg0KIyMjIFN0dWR5IEd1aWRlOiBMb2dpc3RpYyBSZWdyZXNzaW9uIHdpdGggTWF0aGVtYXRpY2FsIGFuZCBDb2RpbmcgUmVwcmVzZW50YXRpb25zDQoNCi0tLQ0KDQojIyMjICoqMS4gQ2F0ZWdvcmljYWwgRGF0YSoqDQotICoqRGVmaW5pdGlvbioqOiBEYXRhIHJlcHJlc2VudGluZyBjbGFzc2VzIG9yIGNhdGVnb3JpZXMgKGUuZy4sIHJlZCwgZ3JlZW4sIGJsdWU7IHRydWUvZmFsc2UpLg0KLSAqKkNoYWxsZW5nZXMqKjogQ2F0ZWdvcmljYWwgZGF0YSBtdXN0IGJlIHRyYW5zZm9ybWVkIGludG8gbnVtZXJpY2FsIGRhdGEgdG8gYmUgdXNlZCBpbiBtb3N0IG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLg0KLSAqKlRyYW5zZm9ybWF0aW9uKio6DQogIC0gKipPbmUtSG90IEVuY29kaW5nKio6IENvbnZlcnRzIGNhdGVnb3JpY2FsIGZlYXR1cmVzIGludG8gYmluYXJ5IGNvbHVtbnMsIHdoZXJlIGVhY2ggdW5pcXVlIHZhbHVlIGJlY29tZXMgYSBjb2x1bW4uIEUuZy4sIGZvciBjb2xvcnMgYHJlZGAsIGBncmVlbmAsIGBibHVlYDoNCiAgICAtIFJlZDogYFsxLCAwLCAwXWANCiAgICAtIEdyZWVuOiBgWzAsIDEsIDBdYA0KICAgIC0gQmx1ZTogYFswLCAwLCAxXWANCg0KKipQeXRob24gRXhhbXBsZSoqOg0KYGBgcHl0aG9uDQppbXBvcnQgcGFuZGFzIGFzIHBkDQoNCmRhdGEgPSB7J0NvbG9yJzogWydSZWQnLCAnR3JlZW4nLCAnQmx1ZSddfQ0KZGYgPSBwZC5EYXRhRnJhbWUoZGF0YSkNCmRmX2VuY29kZWQgPSBwZC5nZXRfZHVtbWllcyhkZiwgY29sdW1ucz1bJ0NvbG9yJ10pDQpgYGANCg0KKipNYXRoZW1hdGljYWwgUmVwcmVzZW50YXRpb24qKjoNCklmIFwoIEMgXCkgcmVwcmVzZW50cyB0aGUgY2F0ZWdvcmllcyBhbmQgXCggeCBcKSBpcyB0aGUgaW5wdXQsIHRoZSB0cmFuc2Zvcm1hdGlvbiBpczoNClxbDQpcdGV4dHtPbmUtaG90IGVuY29kZWQgdmVjdG9yOiB9IFxtYXRoYmZ7eH1fe1x0ZXh0e2VuY29kZWR9fSA9IFt4XzEsIHhfMiwgXGxkb3RzLCB4X25dLCBcdGV4dHt3aGVyZSB9IHhfaSA9IDEgXHRleHR7IGlmIH0geCA9IENfaSwgXHRleHR7IGVsc2UgfSAwLg0KXF0NCg0KLS0tDQoNCiMjIyMgKioyLiBMaW5lYXIgdG8gTG9naXN0aWMgUmVncmVzc2lvbioqDQotIExpbmVhciByZWdyZXNzaW9uIHByZWRpY3RzIGNvbnRpbnVvdXMgdmFsdWVzLiBMb2dpc3RpYyByZWdyZXNzaW9uIHRyYW5zZm9ybXMgdGhpcyB0byBwcmVkaWN0IHByb2JhYmlsaXRpZXMgZm9yIGJpbmFyeSBvdXRjb21lcy4NCi0gKipLZXkgRXF1YXRpb24qKjoNCiAgXFsNCiAgeiA9IFxtYXRoYmZ7d31eVFxtYXRoYmZ7eH0gKyBiLCBccXVhZCBwID0gXHNpZ21hKHopLCBccXVhZCBcc2lnbWEoeikgPSBcZnJhY3sxfXsxICsgZV57LXp9fQ0KICBcXQ0KICBXaGVyZSBcKCBcc2lnbWEoeikgXCkgKHNpZ21vaWQgZnVuY3Rpb24pIHNxdWFzaGVzIFwoIHogXCkgdG8gcmFuZ2UgWzAsIDFdLg0KDQoqKlB5dGhvbiBFeGFtcGxlKio6DQpgYGBweXRob24NCmZyb20gc2tsZWFybi5saW5lYXJfbW9kZWwgaW1wb3J0IExvZ2lzdGljUmVncmVzc2lvbg0KbW9kZWwgPSBMb2dpc3RpY1JlZ3Jlc3Npb24oKQ0KbW9kZWwuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQpgYGANCg0KLS0tDQoNCiMjIyMgKiozLiBUaGUgU2lnbW9pZCBGdW5jdGlvbioqDQotICoqRXF1YXRpb24qKjogXCggXHNpZ21hKHopID0gXGZyYWN7MX17MSArIGVeey16fX0gXCkNCi0gKipQcm9wZXJ0aWVzKio6DQogIC0gXCggeiBcdG8gK1xpbmZ0eSBcKTogXCggXHNpZ21hKHopIFx0byAxIFwpDQogIC0gXCggeiBcdG8gLVxpbmZ0eSBcKTogXCggXHNpZ21hKHopIFx0byAwIFwpDQogIC0gT3V0cHV0cyBwcm9iYWJpbGl0aWVzLg0KDQoqKlB5dGhvbiBFeGFtcGxlKio6DQpgYGBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KDQpkZWYgc2lnbW9pZCh6KToNCiAgICByZXR1cm4gMSAvICgxICsgbnAuZXhwKC16KSkNCg0KeiA9IG5wLmxpbnNwYWNlKC0xMCwgMTAsIDEwMCkNCnNpZ21vaWRfdmFsdWVzID0gc2lnbW9pZCh6KQ0KYGBgDQoNCi0tLQ0KDQojIyMjICoqNC4gTG9nIExvc3MqKg0KLSBNZWFzdXJlcyB0aGUgZGlzdGFuY2UgYmV0d2VlbiBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBhbmQgYWN0dWFsIGNsYXNzIGxhYmVscy4NCi0gKipFcXVhdGlvbioqOg0KICBcWw0KICBcdGV4dHtMb2cgTG9zczogfSBMKHksIFxoYXR7eX0pID0gLVxmcmFjezF9e259IFxzdW1fe2k9MX1ee259IFxsZWZ0WyB5X2kgXGxvZyhcaGF0e3l9X2kpICsgKDEgLSB5X2kpIFxsb2coMSAtIFxoYXR7eX1faSkgXHJpZ2h0XQ0KICBcXQ0KICBXaGVyZToNCiAgLSBcKCB5X2kgXCk6IEFjdHVhbCBsYWJlbC4NCiAgLSBcKCBcaGF0e3l9X2kgXCk6IFByZWRpY3RlZCBwcm9iYWJpbGl0eS4NCg0KKipQeXRob24gRXhhbXBsZSoqOg0KYGBgcHl0aG9uDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgbG9nX2xvc3MNCmxvc3MgPSBsb2dfbG9zcyh5X3RydWUsIHlfcHJlZCkNCmBgYA0KDQotLS0NCg0KIyMjIyAqKjUuIE1pbmltaXppbmcgTG9nIExvc3MqKg0KLSBBY2hpZXZlZCB0aHJvdWdoIG9wdGltaXphdGlvbiAoZS5nLiwgZ3JhZGllbnQgZGVzY2VudCkuDQotICoqR3JhZGllbnQgRGVzY2VudCoqOg0KICAtIFVwZGF0ZSBydWxlOiBcKCB3ID0gdyAtIFxhbHBoYSBcbmFibGEgTCh3KSBcKSwgd2hlcmUgXCggXGFscGhhIFwpIGlzIHRoZSBsZWFybmluZyByYXRlLg0KLSAqKlB5dGhvbiBFeGFtcGxlKio6DQpgYGBweXRob24NCmZyb20gc2tsZWFybi5saW5lYXJfbW9kZWwgaW1wb3J0IExvZ2lzdGljUmVncmVzc2lvbg0KbW9kZWwgPSBMb2dpc3RpY1JlZ3Jlc3Npb24obWF4X2l0ZXI9MTAwKQ0KbW9kZWwuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQpgYGANCg0KLS0tDQoNCiMjIyMgKio2LiBNdWx0aWNsYXNzIENsYXNzaWZpY2F0aW9uKioNCi0gRXh0ZW5kcyBiaW5hcnkgbG9naXN0aWMgcmVncmVzc2lvbiB0byBtdWx0aXBsZSBjbGFzc2VzLg0KLSAqKkFwcHJvYWNoZXMqKjoNCiAgLSAqKk9uZS12cy1SZXN0IChPdlIpKio6IFRyYWluIHNlcGFyYXRlIGNsYXNzaWZpZXJzIGZvciBlYWNoIGNsYXNzLg0KICAtICoqU29mdG1heCBSZWdyZXNzaW9uKio6IERpcmVjdGx5IG1vZGVscyBhbGwgY2xhc3NlcyB1c2luZyBwcm9iYWJpbGl0aWVzLg0KLSAqKlNvZnRtYXggRnVuY3Rpb24qKjoNCiAgXFsNCiAgXHNpZ21hKHpfaikgPSBcZnJhY3tlXnt6X2p9fXtcc3VtX3trPTF9XksgZV57el9rfX0NCiAgXF0NCg0KKipQeXRob24gRXhhbXBsZSoqOg0KYGBgcHl0aG9uDQpmcm9tIHNrbGVhcm4ubGluZWFyX21vZGVsIGltcG9ydCBMb2dpc3RpY1JlZ3Jlc3Npb24NCm1vZGVsID0gTG9naXN0aWNSZWdyZXNzaW9uKG11bHRpX2NsYXNzPSdtdWx0aW5vbWlhbCcsIHNvbHZlcj0nbGJmZ3MnKQ0KbW9kZWwuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQpgYGANCg0KLS0tDQoNCiMjIyBDYXNlIFN0dWR5OiBQcmVkaWN0aW5nIERpYWJldGVzIFJlYWRtaXNzaW9uDQoNCiMjIyMgKipPYmplY3RpdmUqKg0KLSBQcmVkaWN0IGhvc3BpdGFsIHJlYWRtaXNzaW9uIHdpdGhpbiAzMCBkYXlzIHVzaW5nIGxvZ2lzdGljIHJlZ3Jlc3Npb24uDQotIEhhbmRsZSBtaXNzaW5nIGRhdGEgdmlhIGltcHV0YXRpb24uDQoNCiMjIyMgKipTdGVwcyoqOg0KMS4gKipEYXRhIFByZXByb2Nlc3NpbmcqKjoNCiAgIC0gSGFuZGxlIG1pc3NpbmcgdmFsdWVzIChlLmcuLCBtZWFuL21vZGUgaW1wdXRhdGlvbikuDQogICAtIEVuY29kZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgKGUuZy4sIG9uZS1ob3QgZW5jb2RpbmcpLg0KDQoyLiAqKk1vZGVsIEJ1aWxkaW5nKio6DQogICAtIFRyYWluIGxvZ2lzdGljIHJlZ3Jlc3Npb24gZm9yIHRocmVlIGNsYXNzZXMgb2YgcmVhZG1pc3Npb246DQogICAgIC0gTm8gcmVhZG1pc3Npb24uDQogICAgIC0gUmVhZG1pc3Npb24gaW4gbGVzcyB0aGFuIDMwIGRheXMuDQogICAgIC0gUmVhZG1pc3Npb24gaW4gbW9yZSB0aGFuIDMwIGRheXMuDQogICAtIEV2YWx1YXRlIHVzaW5nIG1ldHJpY3MgbGlrZSBsb2cgbG9zcyBvciBhY2N1cmFjeS4NCg0KMy4gKipGZWF0dXJlIEltcG9ydGFuY2UqKjoNCiAgIC0gRXh0cmFjdCBjb2VmZmljaWVudHMgdG8gaWRlbnRpZnkgdG9wIDUgcHJlZGljdGl2ZSBmZWF0dXJlcy4NCg0KKipQeXRob24gQ29kZSoqOg0KYGBgcHl0aG9uDQpmcm9tIHNrbGVhcm4uaW1wdXRlIGltcG9ydCBTaW1wbGVJbXB1dGVyDQpmcm9tIHNrbGVhcm4ubGluZWFyX21vZGVsIGltcG9ydCBMb2dpc3RpY1JlZ3Jlc3Npb24NCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBhY2N1cmFjeV9zY29yZQ0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgdHJhaW5fdGVzdF9zcGxpdA0KDQojIERhdGEgcHJlcHJvY2Vzc2luZw0KaW1wdXRlciA9IFNpbXBsZUltcHV0ZXIoc3RyYXRlZ3k9J21lYW4nKQ0KWCA9IGltcHV0ZXIuZml0X3RyYW5zZm9ybShYKQ0KDQojIFRyYWluLXRlc3Qgc3BsaXQNClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4yKQ0KDQojIExvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwNCm1vZGVsID0gTG9naXN0aWNSZWdyZXNzaW9uKG11bHRpX2NsYXNzPSdtdWx0aW5vbWlhbCcsIHNvbHZlcj0nbGJmZ3MnLCBtYXhfaXRlcj01MDApDQptb2RlbC5maXQoWF90cmFpbiwgeV90cmFpbikNCg0KIyBQcmVkaWN0aW9ucw0KeV9wcmVkID0gbW9kZWwucHJlZGljdChYX3Rlc3QpDQoNCiMgRmVhdHVyZSBpbXBvcnRhbmNlDQppbXBvcnRhbmNlID0gbW9kZWwuY29lZl8NCnByaW50KCJUb3AgNSBGZWF0dXJlczoiLCBpbXBvcnRhbmNlLmFyZ3NvcnQoKVstNTpdKQ0KDQojIEFjY3VyYWN5DQphY2N1cmFjeSA9IGFjY3VyYWN5X3Njb3JlKHlfdGVzdCwgeV9wcmVkKQ0KcHJpbnQoZiJNb2RlbCBBY2N1cmFjeToge2FjY3VyYWN5fSIpDQpgYGANCg0KIyMjIyAqKlZhcmlhYmxlIEltcG9ydGFuY2UgSW50ZXJwcmV0YXRpb24qKg0KLSBDb2VmZmljaWVudHMgcmVmbGVjdCB0aGUgaW1wb3J0YW5jZSBvZiBlYWNoIGZlYXR1cmUuDQotIEFuYWx5emUgdG9wIHZhcmlhYmxlcyB0byBkZXJpdmUgaW5zaWdodHMgZm9yIHJlYWRtaXNzaW9uIHBhdHRlcm5zLg0KDQotLS0NCg0KIyMjIyAqKkFzc2lnbm1lbnQqKg0KLSBUcmFpbiBhbmQgZXZhbHVhdGUgbG9naXN0aWMgcmVncmVzc2lvbiBvbiB0aGUgZGlhYmV0ZXMgZGF0YXNldC4NCi0gUmVwb3J0IHRvcCA1IGZlYXR1cmVzIGFuZCB0aGVpciBzaWduaWZpY2FuY2UuDQotIFN1Ym1pdCByZXN1bHRzIHdpdGggYSBjbGVhciBleHBsYW5hdGlvbiBvZiBmaW5kaW5ncy4NCg0KKipEZWxpdmVyYWJsZSoqOg0KLSBDb2RlIGZpbGUgKGUuZy4sIGBGaXJzdE5hbWVfTGFzdE5hbWVfTG9nUmVnX0Fzc2lnbm1lbnQucHlgKS4NCi0gV3JpdHRlbiByZXBvcnQgb24gbW9kZWwgcGVyZm9ybWFuY2UgYW5kIGZlYXR1cmUgaW1wb3J0YW5jZS4NCg0KDQoNCg0KIyMjICoqS2V5IFRha2Vhd2F5cyoqDQoxLiAqKkxvZ2lzdGljIFJlZ3Jlc3Npb24gQnJpZGdlcyBMaW5lYXIgTW9kZWxzIGFuZCBQcm9iYWJpbGlzdGljIFByZWRpY3Rpb25zKio6DQogICAtIExvZ2lzdGljIHJlZ3Jlc3Npb24gZXh0ZW5kcyBsaW5lYXIgcmVncmVzc2lvbiBmb3IgY2F0ZWdvcmljYWwgdGFyZ2V0IHZhcmlhYmxlcyBieSBlbXBsb3lpbmcgdGhlIHNpZ21vaWQgZnVuY3Rpb24gdG8gb3V0cHV0IHByb2JhYmlsaXRpZXMuIFRoaXMgYWxsb3dzIGZvciBiaW5hcnkgb3IgbXVsdGljbGFzcyBwcmVkaWN0aW9ucyBieSBpbnRlcnByZXRpbmcgcHJvYmFiaWxpdGllcyBhcyBjbGFzcyBtZW1iZXJzaGlwcy4NCg0KMi4gKipMb2cgTG9zcyBhcyBhIE1ldHJpYyoqOg0KICAgLSBVbmxpa2UgbGluZWFyIHJlZ3Jlc3Npb24ncyBtZWFuIHNxdWFyZWQgZXJyb3IsIGxvZ2lzdGljIHJlZ3Jlc3Npb24gdXNlcyBsb2cgbG9zcyB0byBtZWFzdXJlIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlLiBJdCBwZW5hbGl6ZXMgcHJlZGljdGlvbnMgZnVydGhlciBmcm9tIHRoZSB0cnVlIGNsYXNzLCBlbnN1cmluZyBwcm9iYWJpbGl0aWVzIGFyZSBhY2N1cmF0ZS4NCg0KMy4gKipIYW5kbGluZyBNdWx0aWNsYXNzIFByb2JsZW1zKio6DQogICAtIE11bHRpY2xhc3MgY2xhc3NpZmljYXRpb24gY2FuIGJlIGFkZHJlc3NlZCB1c2luZyBtZXRob2RzIGxpa2Ugb25lLXZzLXJlc3QgKE92Uikgb3Igc29mdG1heCByZWdyZXNzaW9uLiBXaGlsZSBPdlIgaXMgY29tcHV0YXRpb25hbGx5IGVmZmljaWVudCBmb3IgYSBmZXcgY2xhc3NlcywgaXQgc2NhbGVzIHBvb3JseSB3aXRoIG1hbnkgY2xhc3NlcyBkdWUgdG8gY2xhc3MgaW1iYWxhbmNlIGlzc3Vlcy4NCiAgIA0KNC4gKipJbXBvcnRhbmNlIG9mIFNpZ21vaWQgRnVuY3Rpb24gaW4gTG9naXN0aWMgUmVncmVzc2lvbioqOiBUaGUgc2lnbW9pZCBmdW5jdGlvbiB0cmFuc2Zvcm1zIHRoZSBsaW5lYXIgcmVncmVzc2lvbiBvdXRwdXQgaW50byBwcm9iYWJpbGl0aWVzLCBtYWtpbmcgaXQgcG9zc2libGUgdG8gaGFuZGxlIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiB0YXNrcyBlZmZlY3RpdmVseS4gVGhpcyBmdW5jdGlvbiBlbnN1cmVzIHRoYXQgcHJlZGljdGlvbnMgZmFsbCB3aXRoaW4gdGhlIHJhbmdlIFswLCAxXSwgd2hpY2ggY2FuIHRoZW4gYmUgdXNlZCB0byBjbGFzc2lmeSBkYXRhIGludG8gZGlzdGluY3QgY2F0ZWdvcmllcy4NCg0KNS4gKipMb2cgTG9zcyBmb3IgQ2xhc3NpZmljYXRpb24qKjogTG9nYXJpdGhtaWMgbG9zcyAobG9nIGxvc3MpIHF1YW50aWZpZXMgdGhlIGVycm9yIGluIHByb2JhYmlsaXN0aWMgcHJlZGljdGlvbnMgYnkgcGVuYWxpemluZyBwcmVkaWN0aW9ucyB0aGF0IGRldmlhdGUgc2lnbmlmaWNhbnRseSBmcm9tIHRoZSB0cnVlIGxhYmVscy4gSXQgZm9ybXMgYSBjb252ZXggZnVuY3Rpb24gd2l0aCBhIHdlbGwtZGVmaW5lZCBtaW5pbXVtLCBmYWNpbGl0YXRpbmcgb3B0aW1pemF0aW9uIGFuZCBjb252ZXJnZW5jZS4NCg0KNi4gKipNdWx0aWNsYXNzIENsYXNzaWZpY2F0aW9uIENoYWxsZW5nZXMqKjogTG9naXN0aWMgcmVncmVzc2lvbiBjYW4gYmUgZXh0ZW5kZWQgdG8gbXVsdGljbGFzcyBwcm9ibGVtcyB0aHJvdWdoIHRlY2huaXF1ZXMgbGlrZSBPbmUtdnMtQWxsIGFuZCBPbmUtdnMtT25lLiBXaGlsZSB0aGVzZSBtZXRob2RzIGFsbG93IGxvZ2lzdGljIHJlZ3Jlc3Npb24gdG8gaGFuZGxlIG1vcmUgdGhhbiB0d28gY2xhc3NlcywgdGhleSBjb21lIHdpdGggc2NhbGFiaWxpdHkgYW5kIGJpYXMgY2hhbGxlbmdlcyBhcyB0aGUgbnVtYmVyIG9mIGNsYXNzZXMgaW5jcmVhc2VzLg0KDQotLS0NCg0KIyMjICoqUXVlc3Rpb25zIGZvciBDbGFzcyBEaXNjdXNzaW9uKioNCjEuICoqVGhyZXNob2xkIEFkanVzdG1lbnQgaW4gTG9naXN0aWMgUmVncmVzc2lvbioqOg0KICAgLSBIb3cgY2FuIGFkanVzdGluZyB0aGUgY2xhc3NpZmljYXRpb24gdGhyZXNob2xkIChlLmcuLCBtb3ZpbmcgaXQgZnJvbSAwLjUgdG8gMC4yIGZvciBmcmF1ZCBkZXRlY3Rpb24pIGltcGFjdCB0aGUgYmFsYW5jZSBiZXR3ZWVuIGZhbHNlIHBvc2l0aXZlcyBhbmQgZmFsc2UgbmVnYXRpdmVzPyBXaGF0IHJlYWwtd29ybGQgZXhhbXBsZXMgaGlnaGxpZ2h0IHRoZSBpbXBvcnRhbmNlIG9mIHRoaXM/DQoNCjIuICoqTG9nIExvc3MgdnMuIEFjY3VyYWN5Kio6DQogICAtIEluIHdoYXQgc2NlbmFyaW9zIG1pZ2h0IGxvZyBsb3NzIGJlIGEgbW9yZSBhcHByb3ByaWF0ZSBldmFsdWF0aW9uIG1ldHJpYyB0aGFuIGFjY3VyYWN5PyBDYW4geW91IHByb3ZpZGUgZXhhbXBsZXMgd2hlcmUgYWNjdXJhY3kgbWlnaHQgYmUgbWlzbGVhZGluZz8NCg0KMy4gKipFdGhpY2FsIENvbnNpZGVyYXRpb25zIGluIENhdGVnb3JpY2FsIFZhcmlhYmxlcyoqOg0KICAgLSBXaGVuIGluY2x1ZGluZyBzZW5zaXRpdmUgdmFyaWFibGVzIGxpa2UgcmFjZSBvciBnZW5kZXIgaW4gbG9naXN0aWMgcmVncmVzc2lvbiwgaG93IGNhbiB3ZSBlbnN1cmUgdGhlIG1vZGVsIGlzIGV0aGljYWxseSBzb3VuZCBhbmQgYXZvaWRzIGRpc2NyaW1pbmF0b3J5IHByYWN0aWNlcyB3aGlsZSBsZXZlcmFnaW5nIHBvdGVudGlhbGx5IHByZWRpY3RpdmUgaW5mb3JtYXRpb24/DQogICANCjQuICoqT24gU2lnbW9pZCBUaHJlc2hvbGQgQWRqdXN0bWVudCoqOiBJbiByZWFsLXdvcmxkIGFwcGxpY2F0aW9ucyBsaWtlIGZyYXVkIGRldGVjdGlvbiwgaG93IGRvIHdlIGRlY2lkZSBvbiB0aGUgb3B0aW1hbCB0aHJlc2hvbGQgZm9yIGNsYXNzaWZpY2F0aW9uIGJleW9uZCB0aGUgZGVmYXVsdCB2YWx1ZSBvZiAwLjU/IFdoYXQgc3RyYXRlZ2llcyBvciBtZXRyaWNzIHNob3VsZCBndWlkZSB0aGlzIGRlY2lzaW9uPw0KDQo1LiAqKk9uIEhhbmRsaW5nIE1pc3NpbmcgRGF0YSoqOiBXaGVuIGRlYWxpbmcgd2l0aCBtaXNzaW5nIHZhbHVlcyBpbiBhIGRhdGFzZXQsIGFzIG1lbnRpb25lZCBpbiB0aGUgZGlhYmV0ZXMgY2FzZSBzdHVkeSwgd2hhdCBmYWN0b3JzIHNob3VsZCBpbmZsdWVuY2UgdGhlIGNob2ljZSBvZiBhbiBpbXB1dGF0aW9uIHN0cmF0ZWd5PyBIb3cgZG8gd2UgZW5zdXJlIHRoZSBpbXB1dGF0aW9uIGRvZXMgbm90IGludHJvZHVjZSBiaWFzIGludG8gdGhlIG1vZGVsPw0KDQo2LiAqKk9uIEV0aGljYWwgQ29uc2lkZXJhdGlvbnMgaW4gRmVhdHVyZSBTZWxlY3Rpb24qKjogSW4gY2FzZXMgd2hlcmUgc2Vuc2l0aXZlIGZlYXR1cmVzIGxpa2UgcmFjZSBhcmUgaW5jbHVkZWQgaW4gdGhlIGRhdGFzZXQsIGhvdyBkbyB3ZSBiYWxhbmNlIHRoZSBwb3RlbnRpYWwgdXRpbGl0eSBvZiBzdWNoIGZlYXR1cmVzIHdpdGggZXRoaWNhbCBjb25jZXJucyBhbmQgdGhlIHJpc2sgb2YgcGVycGV0dWF0aW5nIGJpYXMgaW4gcHJlZGljdGlvbnM/DQoNCg0KICAgDQojIyMgKipCZXN0IFRha2Vhd2F5cyoqDQoNCjEuICoqTG9naXN0aWMgUmVncmVzc2lvbiBCcmlkZ2VzIExpbmVhciBNb2RlbHMgYW5kIFByb2JhYmlsaXN0aWMgUHJlZGljdGlvbnMqKjoNCiAgIC0gTG9naXN0aWMgcmVncmVzc2lvbiBleHRlbmRzIGxpbmVhciByZWdyZXNzaW9uIGZvciBjYXRlZ29yaWNhbCB0YXJnZXQgdmFyaWFibGVzIGJ5IGVtcGxveWluZyB0aGUgc2lnbW9pZCBmdW5jdGlvbiB0byBvdXRwdXQgcHJvYmFiaWxpdGllcy4gVGhpcyBhbGxvd3MgZm9yIGJpbmFyeSBvciBtdWx0aWNsYXNzIHByZWRpY3Rpb25zIGJ5IGludGVycHJldGluZyBwcm9iYWJpbGl0aWVzIGFzIGNsYXNzIG1lbWJlcnNoaXBzLg0KDQoyLiAqKkxvZyBMb3NzIGZvciBDbGFzc2lmaWNhdGlvbioqOg0KICAgLSBMb2dhcml0aG1pYyBsb3NzIChsb2cgbG9zcykgcXVhbnRpZmllcyB0aGUgZXJyb3IgaW4gcHJvYmFiaWxpc3RpYyBwcmVkaWN0aW9ucyBieSBwZW5hbGl6aW5nIHByZWRpY3Rpb25zIHRoYXQgZGV2aWF0ZSBzaWduaWZpY2FudGx5IGZyb20gdGhlIHRydWUgbGFiZWxzLiBJdCBmb3JtcyBhIGNvbnZleCBmdW5jdGlvbiB3aXRoIGEgd2VsbC1kZWZpbmVkIG1pbmltdW0sIGZhY2lsaXRhdGluZyBvcHRpbWl6YXRpb24gYW5kIGNvbnZlcmdlbmNlLg0KDQotLS0NCg0KIyMjICoqQmVzdCBRdWVzdGlvbnMgZm9yIENsYXNzIERpc2N1c3Npb24qKg0KDQoxLiAqKlRocmVzaG9sZCBBZGp1c3RtZW50IGluIExvZ2lzdGljIFJlZ3Jlc3Npb24qKjoNCiAgIC0gSG93IGNhbiBhZGp1c3RpbmcgdGhlIGNsYXNzaWZpY2F0aW9uIHRocmVzaG9sZCAoZS5nLiwgbW92aW5nIGl0IGZyb20gMC41IHRvIDAuMiBmb3IgZnJhdWQgZGV0ZWN0aW9uKSBpbXBhY3QgdGhlIGJhbGFuY2UgYmV0d2VlbiBmYWxzZSBwb3NpdGl2ZXMgYW5kIGZhbHNlIG5lZ2F0aXZlcz8gV2hhdCByZWFsLXdvcmxkIGV4YW1wbGVzIGhpZ2hsaWdodCB0aGUgaW1wb3J0YW5jZSBvZiB0aGlzPw0KDQoyLiAqKkV0aGljYWwgQ29uc2lkZXJhdGlvbnMgaW4gQ2F0ZWdvcmljYWwgVmFyaWFibGVzKio6DQogICAtIFdoZW4gaW5jbHVkaW5nIHNlbnNpdGl2ZSB2YXJpYWJsZXMgbGlrZSByYWNlIG9yIGdlbmRlciBpbiBsb2dpc3RpYyByZWdyZXNzaW9uLCBob3cgY2FuIHdlIGVuc3VyZSB0aGUgbW9kZWwgaXMgZXRoaWNhbGx5IHNvdW5kIGFuZCBhdm9pZHMgZGlzY3JpbWluYXRvcnkgcHJhY3RpY2VzIHdoaWxlIGxldmVyYWdpbmcgcG90ZW50aWFsbHkgcHJlZGljdGl2ZSBpbmZvcm1hdGlvbj8NCg0KDQo=