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:
- Data Preparation:
- Import dataset from
sklearn.datasets
.
- Convert data to a DataFrame and add column names.
- Separate features (X) and targets (y).
- Logistic Regression:
- Use
LogisticRegressionCV
for cross-validation.
- Fit the model:
model.fit(X, y)
.
- Retrieve model coefficients with
model.coef_
.
- 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:
- Data Preprocessing:
- Handle missing data using imputation techniques (e.g., mean,
median).
- Normalize/scale features for better model performance.
- Model Development:
- Build a logistic regression model for each target class (multiclass
setup).
- Use cross-validation to evaluate performance.
- Feature Importance:
- Analyze the top 5 important features contributing to predictions
using model coefficients.
Mathematical and Coding Representations
Mathematical Representation
- Sigmoid Function:
\(\sigma(x) = \frac{1}{1 +
e^{-x}}\)
- Log Loss:
\(J = -\frac{1}{N} \sum_{i=1}^N \left[ y_i
\log(p_i) + (1-y_i) \log(1-p_i) \right]\)
- Gradient Update:
\(m_{new} = m_{old} - \alpha \frac{\partial
J}{\partial m}\)
Python Code Representation
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)
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))
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:
- Data Preprocessing:
- Handle missing values (e.g., mean/mode imputation).
- Encode categorical variables (e.g., one-hot encoding).
- 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.
- 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
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.
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.
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.
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.
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.
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
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?
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?
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?
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?
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?
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
- 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.
- 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
- 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?
- 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=