From 5ddf4c5c6f566fd98ca61f794c0402d1d0a7ce56 Mon Sep 17 00:00:00 2001 From: yonishelach Date: Tue, 18 Jun 2024 12:15:59 +0300 Subject: [PATCH] [Feature-selection] Replace matplotlib with plotly --- feature_selection/feature_selection.py | 52 +++++-------------- feature_selection/function.yaml | 17 +++---- feature_selection/item.yaml | 4 +- feature_selection/requirements.txt | 4 +- feature_selection/test_feature_selection.py | 56 ++++++++++++++------- 5 files changed, 59 insertions(+), 74 deletions(-) diff --git a/feature_selection/feature_selection.py b/feature_selection/feature_selection.py index 630a09694..30fa8f904 100644 --- a/feature_selection/feature_selection.py +++ b/feature_selection/feature_selection.py @@ -13,17 +13,15 @@ # limitations under the License. # import json -import os -import matplotlib.pyplot as plt import mlrun import mlrun.datastore -import mlrun.utils import mlrun.feature_store as fs +import mlrun.utils import numpy as np import pandas as pd -import seaborn as sns -from mlrun.artifacts import PlotArtifact +import plotly.express as px +from mlrun.artifacts import PlotlyArtifact from mlrun.datastore.targets import ParquetTarget # MLRun utils from mlrun.utils.helpers import create_class @@ -42,15 +40,6 @@ } -def _clear_current_figure(): - """ - Clear matplotlib current figure. - """ - plt.cla() - plt.clf() - plt.close() - - def show_values_on_bars(axs, h_v="v", space=0.4): def _show_on_single_plot(ax_): if h_v == "v": @@ -74,33 +63,18 @@ def _show_on_single_plot(ax_): def plot_stat(context, stat_name, stat_df): - _clear_current_figure() - - # Add chart - ax = plt.axes() - stat_chart = sns.barplot( + sorted_df = stat_df.sort_values(stat_name) + fig = px.bar( + data_frame=sorted_df, x=stat_name, - y="index", - data=stat_df.sort_values(stat_name, ascending=False).reset_index(), - ax=ax, + y=sorted_df.index, + title=f"{stat_name} feature scores", + color=stat_name, ) - plt.tight_layout() - - for p in stat_chart.patches: - width = p.get_width() - plt.text( - 5 + p.get_width(), - p.get_y() + 0.55 * p.get_height(), - "{:1.2f}".format(width), - ha="center", - va="center", - ) - context.log_artifact( - PlotArtifact(f"{stat_name}", body=plt.gcf()), - local_path=os.path.join("plots", "feature_selection", f"{stat_name}.html"), + item=PlotlyArtifact(key=stat_name, figure=fig), + local_path=f"{stat_name}.html", ) - _clear_current_figure() def feature_selection( @@ -115,7 +89,6 @@ def feature_selection( sample_ratio: float = None, output_vector_name: float = None, ignore_type_errors: bool = False, - is_feature_vector: bool = False, ): """ Applies selected feature selection statistical functions or models on our 'df_artifact'. @@ -138,10 +111,9 @@ def feature_selection( model name (ex. LinearSVC), formalized json (contains 'CLASS', 'FIT', 'META') or a path to such json file. :param max_scaled_scores: produce feature scores table scaled with max_scaler. - :param sample_ratio: percentage of the dataset the user whishes to compute the feature selection process on. + :param sample_ratio: percentage of the dataset the user wishes to compute the feature selection process on. :param output_vector_name: creates a new feature vector containing only the identifies features. :param ignore_type_errors: skips datatypes that are neither float nor int within the feature vector. - :param is_feature_vector: bool stating if the data is passed as a feature vector. """ stat_filters = stat_filters or DEFAULT_STAT_FILTERS model_filters = model_filters or DEFAULT_MODEL_FILTERS diff --git a/feature_selection/function.yaml b/feature_selection/function.yaml index 0851f54d3..aca1f0c0c 100644 --- a/feature_selection/function.yaml +++ b/feature_selection/function.yaml @@ -2,7 +2,7 @@ kind: job metadata: name: feature-selection tag: '' - hash: 6dba16d062d81f78d3d210fee75edfe8b1def9b3 + hash: 5815ef4c27a1f08c9d8d3f88ad6bd4c9cb5c7f4a project: '' labels: author: orz @@ -14,7 +14,7 @@ spec: args: [] image: mlrun/mlrun build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IGpzb24KaW1wb3J0IG9zCgppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0CmltcG9ydCBtbHJ1bgppbXBvcnQgbWxydW4uZGF0YXN0b3JlCmltcG9ydCBtbHJ1bi51dGlscwppbXBvcnQgbWxydW4uZmVhdHVyZV9zdG9yZSBhcyBmcwppbXBvcnQgbnVtcHkgYXMgbnAKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgc2VhYm9ybiBhcyBzbnMKZnJvbSBtbHJ1bi5hcnRpZmFjdHMgaW1wb3J0IFBsb3RBcnRpZmFjdApmcm9tIG1scnVuLmRhdGFzdG9yZS50YXJnZXRzIGltcG9ydCBQYXJxdWV0VGFyZ2V0CiMgTUxSdW4gdXRpbHMKZnJvbSBtbHJ1bi51dGlscy5oZWxwZXJzIGltcG9ydCBjcmVhdGVfY2xhc3MKIyBGZWF0dXJlIHNlbGVjdGlvbiBzdHJhdGVnaWVzCmZyb20gc2tsZWFybi5mZWF0dXJlX3NlbGVjdGlvbiBpbXBvcnQgU2VsZWN0RnJvbU1vZGVsLCBTZWxlY3RLQmVzdAojIFNjYWxlIGZlYXR1cmUgc2NvcmVzZ2l0IHN0CmZyb20gc2tsZWFybi5wcmVwcm9jZXNzaW5nIGltcG9ydCBNaW5NYXhTY2FsZXIKIyBTS0xlYXJuIGVzdGltYXRvcnMgbGlzdApmcm9tIHNrbGVhcm4udXRpbHMgaW1wb3J0IGFsbF9lc3RpbWF0b3JzCgpERUZBVUxUX1NUQVRfRklMVEVSUyA9IFsiZl9jbGFzc2lmIiwgIm11dHVhbF9pbmZvX2NsYXNzaWYiLCAiY2hpMiIsICJmX3JlZ3Jlc3Npb24iXQpERUZBVUxUX01PREVMX0ZJTFRFUlMgPSB7CiAgICAiTGluZWFyU1ZDIjogIkxpbmVhclNWQyIsCiAgICAiTG9naXN0aWNSZWdyZXNzaW9uIjogIkxvZ2lzdGljUmVncmVzc2lvbiIsCiAgICAiRXh0cmFUcmVlc0NsYXNzaWZpZXIiOiAiRXh0cmFUcmVlc0NsYXNzaWZpZXIiLAp9CgoKZGVmIF9jbGVhcl9jdXJyZW50X2ZpZ3VyZSgpOgogICAgIiIiCiAgICBDbGVhciBtYXRwbG90bGliIGN1cnJlbnQgZmlndXJlLgogICAgIiIiCiAgICBwbHQuY2xhKCkKICAgIHBsdC5jbGYoKQogICAgcGx0LmNsb3NlKCkKCgpkZWYgc2hvd192YWx1ZXNfb25fYmFycyhheHMsIGhfdj0idiIsIHNwYWNlPTAuNCk6CiAgICBkZWYgX3Nob3dfb25fc2luZ2xlX3Bsb3QoYXhfKToKICAgICAgICBpZiBoX3YgPT0gInYiOgogICAgICAgICAgICBmb3IgcCBpbiBheF8ucGF0Y2hlczoKICAgICAgICAgICAgICAgIF94ID0gcC5nZXRfeCgpICsgcC5nZXRfd2lkdGgoKSAvIDIKICAgICAgICAgICAgICAgIF95ID0gcC5nZXRfeSgpICsgcC5nZXRfaGVpZ2h0KCkKICAgICAgICAgICAgICAgIHZhbHVlID0gaW50KHAuZ2V0X2hlaWdodCgpKQogICAgICAgICAgICAgICAgYXhfLnRleHQoX3gsIF95LCB2YWx1ZSwgaGE9ImNlbnRlciIpCiAgICAgICAgZWxpZiBoX3YgPT0gImgiOgogICAgICAgICAgICBmb3IgcCBpbiBheF8ucGF0Y2hlczoKICAgICAgICAgICAgICAgIF94ID0gcC5nZXRfeCgpICsgcC5nZXRfd2lkdGgoKSArIGZsb2F0KHNwYWNlKQogICAgICAgICAgICAgICAgX3kgPSBwLmdldF95KCkgKyBwLmdldF9oZWlnaHQoKQogICAgICAgICAgICAgICAgdmFsdWUgPSBpbnQocC5nZXRfd2lkdGgoKSkKICAgICAgICAgICAgICAgIGF4Xy50ZXh0KF94LCBfeSwgdmFsdWUsIGhhPSJsZWZ0IikKCiAgICBpZiBpc2luc3RhbmNlKGF4cywgbnAubmRhcnJheSk6CiAgICAgICAgZm9yIGlkeCwgYXggaW4gbnAubmRlbnVtZXJhdGUoYXhzKToKICAgICAgICAgICAgX3Nob3dfb25fc2luZ2xlX3Bsb3QoYXgpCiAgICBlbHNlOgogICAgICAgIF9zaG93X29uX3NpbmdsZV9wbG90KGF4cykKCgpkZWYgcGxvdF9zdGF0KGNvbnRleHQsIHN0YXRfbmFtZSwgc3RhdF9kZik6CiAgICBfY2xlYXJfY3VycmVudF9maWd1cmUoKQoKICAgICMgQWRkIGNoYXJ0CiAgICBheCA9IHBsdC5heGVzKCkKICAgIHN0YXRfY2hhcnQgPSBzbnMuYmFycGxvdCgKICAgICAgICB4PXN0YXRfbmFtZSwKICAgICAgICB5PSJpbmRleCIsCiAgICAgICAgZGF0YT1zdGF0X2RmLnNvcnRfdmFsdWVzKHN0YXRfbmFtZSwgYXNjZW5kaW5nPUZhbHNlKS5yZXNldF9pbmRleCgpLAogICAgICAgIGF4PWF4LAogICAgKQogICAgcGx0LnRpZ2h0X2xheW91dCgpCgogICAgZm9yIHAgaW4gc3RhdF9jaGFydC5wYXRjaGVzOgogICAgICAgIHdpZHRoID0gcC5nZXRfd2lkdGgoKQogICAgICAgIHBsdC50ZXh0KAogICAgICAgICAgICA1ICsgcC5nZXRfd2lkdGgoKSwKICAgICAgICAgICAgcC5nZXRfeSgpICsgMC41NSAqIHAuZ2V0X2hlaWdodCgpLAogICAgICAgICAgICAiezoxLjJmfSIuZm9ybWF0KHdpZHRoKSwKICAgICAgICAgICAgaGE9ImNlbnRlciIsCiAgICAgICAgICAgIHZhPSJjZW50ZXIiLAogICAgICAgICkKCiAgICBjb250ZXh0LmxvZ19hcnRpZmFjdCgKICAgICAgICBQbG90QXJ0aWZhY3QoZiJ7c3RhdF9uYW1lfSIsIGJvZHk9cGx0LmdjZigpKSwKICAgICAgICBsb2NhbF9wYXRoPW9zLnBhdGguam9pbigicGxvdHMiLCAiZmVhdHVyZV9zZWxlY3Rpb24iLCBmIntzdGF0X25hbWV9Lmh0bWwiKSwKICAgICkKICAgIF9jbGVhcl9jdXJyZW50X2ZpZ3VyZSgpCgoKZGVmIGZlYXR1cmVfc2VsZWN0aW9uKAogICAgY29udGV4dCwKICAgIGRmX2FydGlmYWN0LAogICAgazogaW50ID0gNSwKICAgIG1pbl92b3RlczogZmxvYXQgPSAwLjUsCiAgICBsYWJlbF9jb2x1bW46IHN0ciA9IE5vbmUsCiAgICBzdGF0X2ZpbHRlcnM6IGxpc3QgPSBOb25lLAogICAgbW9kZWxfZmlsdGVyczogZGljdCA9IE5vbmUsCiAgICBtYXhfc2NhbGVkX3Njb3JlczogYm9vbCA9IFRydWUsCiAgICBzYW1wbGVfcmF0aW86IGZsb2F0ID0gTm9uZSwKICAgIG91dHB1dF92ZWN0b3JfbmFtZTogZmxvYXQgPSBOb25lLAogICAgaWdub3JlX3R5cGVfZXJyb3JzOiBib29sID0gRmFsc2UsCiAgICBpc19mZWF0dXJlX3ZlY3RvcjogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBBcHBsaWVzIHNlbGVjdGVkIGZlYXR1cmUgc2VsZWN0aW9uIHN0YXRpc3RpY2FsIGZ1bmN0aW9ucyBvciBtb2RlbHMgb24gb3VyICdkZl9hcnRpZmFjdCcuCgogICAgRWFjaCBzdGF0aXN0aWNhbCBmdW5jdGlvbiBvciBtb2RlbCB3aWxsIHZvdGUgZm9yIGl0J3MgYmVzdCBLIHNlbGVjdGVkIGZlYXR1cmVzLgogICAgSWYgYSBmZWF0dXJlIGhhcyA+PSAnbWluX3ZvdGVzJyB2b3RlcywgaXQgd2lsbCBiZSBzZWxlY3RlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgdGhlIGZ1bmN0aW9uIGNvbnRleHQuCiAgICA6cGFyYW0gZGZfYXJ0aWZhY3Q6ICAgICAgICAgZGF0YWZyYW1lIHRvIHBhc3MgYXMgaW5wdXQuCiAgICA6cGFyYW0gazogICAgICAgICAgICAgICAgICAgbnVtYmVyIG9mIHRvcCBmZWF0dXJlcyB0byBzZWxlY3QgZnJvbSBlYWNoIHN0YXRpc3RpY2FsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24gb3IgbW9kZWwuCiAgICA6cGFyYW0gbWluX3ZvdGVzOiAgICAgICAgICAgbWluaW1hbCBudW1iZXIgb2Ygdm90ZXMgKGZyb20gYSBtb2RlbCBvciBieSBzdGF0aXN0aWNhbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKSBuZWVkZWQgZm9yIGEgZmVhdHVyZSB0byBiZSBzZWxlY3RlZC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDYW4gYmUgc3BlY2lmaWVkIGJ5IHBlcmNlbnRhZ2Ugb2Ygdm90ZXMgb3IgYWJzb2x1dGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgb2Ygdm90ZXMuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uOiAgICAgICAgZ3JvdW5kLXRydXRoICh5KSBsYWJlbHMuCiAgICA6cGFyYW0gc3RhdF9maWx0ZXJzOiAgICAgICAgc3RhdGlzdGljYWwgZnVuY3Rpb25zIHRvIGFwcGx5IHRvIHRoZSBmZWF0dXJlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChmcm9tIHNrbGVhcm4uZmVhdHVyZV9zZWxlY3Rpb24pLgogICAgOnBhcmFtIG1vZGVsX2ZpbHRlcnM6ICAgICAgIG1vZGVscyB0byB1c2UgZm9yIGZlYXR1cmUgZXZhbHVhdGlvbiwgY2FuIGJlIHNwZWNpZmllZCBieQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsIG5hbWUgKGV4LiBMaW5lYXJTVkMpLCBmb3JtYWxpemVkIGpzb24gKGNvbnRhaW5zICdDTEFTUycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ZJVCcsICdNRVRBJykgb3IgYSBwYXRoIHRvIHN1Y2gganNvbiBmaWxlLgogICAgOnBhcmFtIG1heF9zY2FsZWRfc2NvcmVzOiAgIHByb2R1Y2UgZmVhdHVyZSBzY29yZXMgdGFibGUgc2NhbGVkIHdpdGggbWF4X3NjYWxlci4KICAgIDpwYXJhbSBzYW1wbGVfcmF0aW86ICAgICAgICBwZXJjZW50YWdlIG9mIHRoZSBkYXRhc2V0IHRoZSB1c2VyIHdoaXNoZXMgdG8gY29tcHV0ZSB0aGUgZmVhdHVyZSBzZWxlY3Rpb24gcHJvY2VzcyBvbi4KICAgIDpwYXJhbSBvdXRwdXRfdmVjdG9yX25hbWU6ICBjcmVhdGVzIGEgbmV3IGZlYXR1cmUgdmVjdG9yIGNvbnRhaW5pbmcgb25seSB0aGUgaWRlbnRpZmllcyBmZWF0dXJlcy4KICAgIDpwYXJhbSBpZ25vcmVfdHlwZV9lcnJvcnM6ICBza2lwcyBkYXRhdHlwZXMgdGhhdCBhcmUgbmVpdGhlciBmbG9hdCBub3IgaW50IHdpdGhpbiB0aGUgZmVhdHVyZSB2ZWN0b3IuCiAgICA6cGFyYW0gaXNfZmVhdHVyZV92ZWN0b3I6ICAgYm9vbCBzdGF0aW5nIGlmIHRoZSBkYXRhIGlzIHBhc3NlZCBhcyBhIGZlYXR1cmUgdmVjdG9yLgogICAgIiIiCiAgICBzdGF0X2ZpbHRlcnMgPSBzdGF0X2ZpbHRlcnMgb3IgREVGQVVMVF9TVEFUX0ZJTFRFUlMKICAgIG1vZGVsX2ZpbHRlcnMgPSBtb2RlbF9maWx0ZXJzIG9yIERFRkFVTFRfTU9ERUxfRklMVEVSUwogICAgIyBDaGVjayBpZiBkZi5tZXRhIGlzIHZhbGlkLCBpZiBpdCBpcywgbG9vayBmb3IgYSBmZWF0dXJlIHZlY3RvcgogICAgc3RvcmVfdXJpX3ByZWZpeCwgXyA9IG1scnVuLmRhdGFzdG9yZS5wYXJzZV9zdG9yZV91cmkoZGZfYXJ0aWZhY3QuYXJ0aWZhY3RfdXJsKQogICAgaXNfZmVhdHVyZV92ZWN0b3IgPSBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXgKCiAgICAjIExvb2sgaW5zaWRlIG1ldGEuc3BlYy5sYWJlbF9mZWF0dXJlIHRvIGlkZW50aWZ5IHRoZSBsYWJlbF9jb2x1bW4gaWYgdGhlIHVzZXIgZGlkIG5vdCBzcGVjaWZ5IGl0CiAgICBpZiBsYWJlbF9jb2x1bW4gaXMgTm9uZToKICAgICAgICBpZiBpc19mZWF0dXJlX3ZlY3RvcjoKICAgICAgICAgICAgbGFiZWxfY29sdW1uID0gZGZfYXJ0aWZhY3QubWV0YS5zcGVjLmxhYmVsX2ZlYXR1cmUuc3BsaXQoIi4iKVsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIk5vIGxhYmVsX2NvbHVtbiB3YXMgZ2l2ZW4sIHBsZWFzZSBhZGQgYSBsYWJlbF9jb2x1bW4uIikKCiAgICAjIFVzZSB0aGUgZmVhdHVyZSB2ZWN0b3IgYXMgZGF0YWZyYW1lCiAgICBkZiA9IGRmX2FydGlmYWN0LmFzX2RmKCkKCiAgICAjIEVuc3VyZSBrIGlzIG5vdCBiaWdnZXIgdGhhbiB0aGUgdG90YWwgbnVtYmVyIG9mIGZlYXR1cmVzCiAgICBpZiBrID4gZGYuc2hhcGVbMV06CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJLIGNhbm5vdCBiZSBiaWdnZXIgdGhhbiB0aGUgdG90YWwgbnVtYmVyIG9mIGZlYXR1cmVzICh7ZGYuc2hhcGVbMV19KS4gUGxlYXNlIGNob29zZSBhIHNtYWxsZXIgSy4iCiAgICAgICAgKQogICAgZWxpZiBrIDwgMToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCJLIGNhbm5vdCBiZSBzbWFsbGVyIHRoYW4gMS4gUGxlYXNlIGNob29zZSBhIGJpZ2dlciBLLiIpCgogICAgIyBDcmVhdGUgYSBzYW1wbGUgZGF0YWZyYW1lIG9mIHRoZSBvcmlnaW5hbCBmZWF0dXJlIHZlY3RvcgogICAgaWYgc2FtcGxlX3JhdGlvOgogICAgICAgIGRmID0gKAogICAgICAgICAgICBkZi5ncm91cGJ5KGxhYmVsX2NvbHVtbikKICAgICAgICAgICAgLmFwcGx5KGxhbWJkYSB4OiB4LnNhbXBsZShmcmFjPXNhbXBsZV9yYXRpbykpCiAgICAgICAgICAgIC5yZXNldF9pbmRleChkcm9wPVRydWUpCiAgICAgICAgKQogICAgICAgIGRmID0gZGYuZHJvcG5hKCkKCiAgICAjIFNldCBmZWF0dXJlIHZlY3RvciBhbmQgbGFiZWxzCiAgICB5ID0gZGYucG9wKGxhYmVsX2NvbHVtbikKICAgIFggPSBkZgoKICAgIGlmIG5wLm9iamVjdF8gaW4gbGlzdChYLmR0eXBlcykgYW5kIGlnbm9yZV90eXBlX2Vycm9ycyBpcyBGYWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIntkZi5zZWxlY3RfZHR5cGVzKGluY2x1ZGU9WydvYmplY3QnXSkuY29sdW1ucy50b2xpc3QoKX0gYXJlIG5laXRoZXIgZmxvYXQgb3IgaW50LiIKICAgICAgICApCgogICAgIyBDcmVhdGUgc2VsZWN0ZWQgc3RhdGlzdGljYWwgZXN0aW1hdG9ycwogICAgc3RhdF9mdW5jdGlvbnNfbGlzdCA9IHsKICAgICAgICBzdGF0X25hbWU6IFNlbGVjdEtCZXN0KAogICAgICAgICAgICBzY29yZV9mdW5jPWNyZWF0ZV9jbGFzcyhmInNrbGVhcm4uZmVhdHVyZV9zZWxlY3Rpb24ue3N0YXRfbmFtZX0iKSwgaz1rCiAgICAgICAgKQogICAgICAgIGZvciBzdGF0X25hbWUgaW4gc3RhdF9maWx0ZXJzCiAgICB9CiAgICByZXF1aXJlc19hYnMgPSBbImNoaTIiXQoKICAgICMgUnVuIHN0YXRpc3RpYyBmaWx0ZXJzCiAgICBzZWxlY3RlZF9mZWF0dXJlc19hZ2cgPSB7fQogICAgc3RhdHNfZGYgPSBwZC5EYXRhRnJhbWUoaW5kZXg9WC5jb2x1bW5zKS5kcm9wbmEoKQoKICAgIGZvciBzdGF0X25hbWUsIHN0YXRfZnVuYyBpbiBzdGF0X2Z1bmN0aW9uc19saXN0Lml0ZW1zKCk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBwYXJhbXMgPSAoWCwgeSkgaWYgc3RhdF9uYW1lIGluIHJlcXVpcmVzX2FicyBlbHNlIChhYnMoWCksIHkpCiAgICAgICAgICAgIHN0YXQgPSBzdGF0X2Z1bmMuZml0KCpwYXJhbXMpCgogICAgICAgICAgICAjIENvbGxlY3Qgc3RhdCBmdW5jdGlvbiByZXN1bHRzCiAgICAgICAgICAgIHN0YXRfZGYgPSBwZC5EYXRhRnJhbWUoCiAgICAgICAgICAgICAgICBpbmRleD1YLmNvbHVtbnMsIGNvbHVtbnM9W3N0YXRfbmFtZV0sIGRhdGE9c3RhdC5zY29yZXNfCiAgICAgICAgICAgICkKICAgICAgICAgICAgcGxvdF9zdGF0KGNvbnRleHQsIHN0YXRfbmFtZSwgc3RhdF9kZikKICAgICAgICAgICAgc3RhdHNfZGYgPSBzdGF0c19kZi5qb2luKHN0YXRfZGYpCgogICAgICAgICAgICAjIFNlbGVjdCBLIEJlc3QgZmVhdHVyZXMKICAgICAgICAgICAgc2VsZWN0ZWRfZmVhdHVyZXMgPSBYLmNvbHVtbnNbc3RhdF9mdW5jLmdldF9zdXBwb3J0KCldCiAgICAgICAgICAgIHNlbGVjdGVkX2ZlYXR1cmVzX2FnZ1tzdGF0X25hbWVdID0gc2VsZWN0ZWRfZmVhdHVyZXMKCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiQ291bGRuJ3QgY2FsY3VsYXRlIHtzdGF0X25hbWV9IGJlY2F1c2Ugb2Y6IHtlfSIpCgogICAgIyBDcmVhdGUgbW9kZWxzIGZyb20gY2xhc3MgbmFtZSAvIGpzb24gZmlsZSAvIGpzb24gcGFyYW1zCiAgICBhbGxfc2tsZWFybl9lc3RpbWF0b3JzID0gZGljdChhbGxfZXN0aW1hdG9ycygpKSBpZiBsZW4obW9kZWxfZmlsdGVycykgPiAwIGVsc2Uge30KICAgIHNlbGVjdGVkX21vZGVscyA9IHt9CiAgICBmb3IgbW9kZWxfbmFtZSwgbW9kZWwgaW4gbW9kZWxfZmlsdGVycy5pdGVtcygpOgogICAgICAgIGlmICIuanNvbiIgaW4gbW9kZWw6CiAgICAgICAgICAgIGN1cnJlbnRfbW9kZWwgPSBqc29uLmxvYWQob3Blbihtb2RlbCwgInIiKSkKICAgICAgICAgICAgY2xhc3NpZmllcl9jbGFzcyA9IGNyZWF0ZV9jbGFzcyhjdXJyZW50X21vZGVsWyJNRVRBIl1bImNsYXNzIl0pCiAgICAgICAgICAgIHNlbGVjdGVkX21vZGVsc1ttb2RlbF9uYW1lXSA9IGNsYXNzaWZpZXJfY2xhc3MoKipjdXJyZW50X21vZGVsWyJDTEFTUyJdKQogICAgICAgIGVsaWYgbW9kZWwgaW4gYWxsX3NrbGVhcm5fZXN0aW1hdG9yczoKICAgICAgICAgICAgc2VsZWN0ZWRfbW9kZWxzW21vZGVsX25hbWVdID0gYWxsX3NrbGVhcm5fZXN0aW1hdG9yc1ttb2RlbF9uYW1lXSgpCgogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgIGN1cnJlbnRfbW9kZWwgPSBqc29uLmxvYWRzKG1vZGVsKQogICAgICAgICAgICAgICAgY2xhc3NpZmllcl9jbGFzcyA9IGNyZWF0ZV9jbGFzcyhjdXJyZW50X21vZGVsWyJNRVRBIl1bImNsYXNzIl0pCiAgICAgICAgICAgICAgICBzZWxlY3RlZF9tb2RlbHNbbW9kZWxfbmFtZV0gPSBjbGFzc2lmaWVyX2NsYXNzKCoqY3VycmVudF9tb2RlbFsiQ0xBU1MiXSkKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbyhmInVuYWJsZSB0byBsb2FkIHttb2RlbH0gYmVjYXVzZSBvZjoge2V9IikKCiAgICAjIFJ1biBtb2RlbCBmaWx0ZXJzCiAgICBtb2RlbHNfZGYgPSBwZC5EYXRhRnJhbWUoaW5kZXg9WC5jb2x1bW5zKQogICAgZm9yIG1vZGVsX25hbWUsIG1vZGVsIGluIHNlbGVjdGVkX21vZGVscy5pdGVtcygpOgoKICAgICAgICBpZiBtb2RlbF9uYW1lID09ICJMb2dpc3RpY1JlZ3Jlc3Npb24iOgogICAgICAgICAgICBtb2RlbC5zZXRfcGFyYW1zKHNvbHZlcj0ibGlibGluZWFyIikKCiAgICAgICAgIyBUcmFpbiBtb2RlbCBhbmQgZ2V0IGZlYXR1cmUgaW1wb3J0YW5jZQogICAgICAgIHNlbGVjdF9mcm9tX21vZGVsID0gU2VsZWN0RnJvbU1vZGVsKG1vZGVsKS5maXQoWCwgeSkKICAgICAgICBmZWF0dXJlX2lkeCA9IHNlbGVjdF9mcm9tX21vZGVsLmdldF9zdXBwb3J0KCkKICAgICAgICBmZWF0dXJlX25hbWVzID0gWC5jb2x1bW5zW2ZlYXR1cmVfaWR4XQogICAgICAgIHNlbGVjdGVkX2ZlYXR1cmVzX2FnZ1ttb2RlbF9uYW1lXSA9IGZlYXR1cmVfbmFtZXMudG9saXN0KCkKCiAgICAgICAgIyBDb2xsZWN0IG1vZGVsIGZlYXR1cmUgaW1wb3J0YW5jZQogICAgICAgIGlmIGhhc2F0dHIoc2VsZWN0X2Zyb21fbW9kZWwuZXN0aW1hdG9yXywgImNvZWZfIik6CiAgICAgICAgICAgIHN0YXRfZGYgPSBzZWxlY3RfZnJvbV9tb2RlbC5lc3RpbWF0b3JfLmNvZWZfCiAgICAgICAgZWxpZiBoYXNhdHRyKHNlbGVjdF9mcm9tX21vZGVsLmVzdGltYXRvcl8sICJmZWF0dXJlX2ltcG9ydGFuY2VzXyIpOgogICAgICAgICAgICBzdGF0X2RmID0gc2VsZWN0X2Zyb21fbW9kZWwuZXN0aW1hdG9yXy5mZWF0dXJlX2ltcG9ydGFuY2VzXwoKICAgICAgICBzdGF0X2RmID0gcGQuRGF0YUZyYW1lKGluZGV4PVguY29sdW1ucywgY29sdW1ucz1bbW9kZWxfbmFtZV0sIGRhdGE9c3RhdF9kZlswXSkKICAgICAgICBtb2RlbHNfZGYgPSBtb2RlbHNfZGYuam9pbihzdGF0X2RmKQoKICAgICAgICBwbG90X3N0YXQoY29udGV4dCwgbW9kZWxfbmFtZSwgc3RhdF9kZikKCiAgICAjIENyZWF0ZSBmZWF0dXJlX3Njb3JlcyBERiB3aXRoIHN0YXQgJiBtb2RlbCBmaWx0ZXJzIHNjb3JlcwogICAgcmVzdWx0X21hdHJpeF9kZiA9IHBkLmNvbmNhdChbc3RhdHNfZGYsIG1vZGVsc19kZl0sIGF4aXM9MSwgc29ydD1GYWxzZSkKICAgIGNvbnRleHQubG9nX2RhdGFzZXQoCiAgICAgICAga2V5PSJmZWF0dXJlX3Njb3JlcyIsCiAgICAgICAgZGY9cmVzdWx0X21hdHJpeF9kZiwKICAgICAgICBsb2NhbF9wYXRoPSJmZWF0dXJlX3Njb3Jlcy5wYXJxdWV0IiwKICAgICAgICBmb3JtYXQ9InBhcnF1ZXQiLAogICAgKQogICAgaWYgbWF4X3NjYWxlZF9zY29yZXM6CiAgICAgICAgbm9ybWFsaXplZF9kZiA9IHJlc3VsdF9tYXRyaXhfZGYucmVwbGFjZShbbnAuaW5mLCAtbnAuaW5mXSwgbnAubmFuKS52YWx1ZXMKICAgICAgICBtaW5fbWF4X3NjYWxlciA9IE1pbk1heFNjYWxlcigpCiAgICAgICAgbm9ybWFsaXplZF9kZiA9IG1pbl9tYXhfc2NhbGVyLmZpdF90cmFuc2Zvcm0obm9ybWFsaXplZF9kZikKICAgICAgICBub3JtYWxpemVkX2RmID0gcGQuRGF0YUZyYW1lKAogICAgICAgICAgICBkYXRhPW5vcm1hbGl6ZWRfZGYsCiAgICAgICAgICAgIGNvbHVtbnM9cmVzdWx0X21hdHJpeF9kZi5jb2x1bW5zLAogICAgICAgICAgICBpbmRleD1yZXN1bHRfbWF0cml4X2RmLmluZGV4LAogICAgICAgICkKICAgICAgICBjb250ZXh0LmxvZ19kYXRhc2V0KAogICAgICAgICAgICBrZXk9Im1heF9zY2FsZWRfc2NvcmVzX2ZlYXR1cmVfc2NvcmVzIiwKICAgICAgICAgICAgZGY9bm9ybWFsaXplZF9kZiwKICAgICAgICAgICAgbG9jYWxfcGF0aD0ibWF4X3NjYWxlZF9zY29yZXNfZmVhdHVyZV9zY29yZXMucGFycXVldCIsCiAgICAgICAgICAgIGZvcm1hdD0icGFycXVldCIsCiAgICAgICAgKQoKICAgICMgQ3JlYXRlIGZlYXR1cmUgY291bnQgRGF0YUZyYW1lCiAgICBmb3IgdGVzdF9uYW1lIGluIHNlbGVjdGVkX2ZlYXR1cmVzX2FnZzoKICAgICAgICByZXN1bHRfbWF0cml4X2RmW3Rlc3RfbmFtZV0gPSBbCiAgICAgICAgICAgIDEgaWYgeCBpbiBzZWxlY3RlZF9mZWF0dXJlc19hZ2dbdGVzdF9uYW1lXSBlbHNlIDAgZm9yIHggaW4gWC5jb2x1bW5zCiAgICAgICAgXQogICAgcmVzdWx0X21hdHJpeF9kZi5sb2NbOiwgIm51bV92b3RlcyJdID0gcmVzdWx0X21hdHJpeF9kZi5zdW0oYXhpcz0xKQogICAgY29udGV4dC5sb2dfZGF0YXNldCgKICAgICAgICBrZXk9InNlbGVjdGVkX2ZlYXR1cmVzX2NvdW50IiwKICAgICAgICBkZj1yZXN1bHRfbWF0cml4X2RmLAogICAgICAgIGxvY2FsX3BhdGg9InNlbGVjdGVkX2ZlYXR1cmVzX2NvdW50LnBhcnF1ZXQiLAogICAgICAgIGZvcm1hdD0icGFycXVldCIsCiAgICApCgogICAgIyBIb3cgbWFueSB2b3RlcyBhcmUgbmVlZGVkIGZvciBhIGZlYXR1cmUgdG8gYmUgc2VsZWN0ZWQ/CiAgICBpZiBpc2luc3RhbmNlKG1pbl92b3RlcywgaW50KToKICAgICAgICB2b3Rlc19uZWVkZWQgPSBtaW5fdm90ZXMKICAgIGVsc2U6CiAgICAgICAgbnVtX2ZpbHRlcnMgPSBsZW4oc3RhdF9maWx0ZXJzKSArIGxlbihtb2RlbF9maWx0ZXJzKQogICAgICAgIHZvdGVzX25lZWRlZCA9IGludChucC5mbG9vcihudW1fZmlsdGVycyAqIG1heChtaW4obWluX3ZvdGVzLCAxKSwgMCkpKQogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmInZvdGVzIG5lZWRlZCB0byBiZSBzZWxlY3RlZDoge3ZvdGVzX25lZWRlZH0iKQoKICAgICMgQ3JlYXRlIGZpbmFsIGZlYXR1cmUgZGF0YWZyYW1lCiAgICBzZWxlY3RlZF9mZWF0dXJlcyA9IHJlc3VsdF9tYXRyaXhfZGZbCiAgICAgICAgcmVzdWx0X21hdHJpeF9kZi5udW1fdm90ZXMgPj0gdm90ZXNfbmVlZGVkCiAgICBdLmluZGV4LnRvbGlzdCgpCiAgICBnb29kX2ZlYXR1cmVfZGYgPSBkZi5sb2NbOiwgc2VsZWN0ZWRfZmVhdHVyZXNdCiAgICBmaW5hbF9kZiA9IHBkLmNvbmNhdChbZ29vZF9mZWF0dXJlX2RmLCB5XSwgYXhpcz0xKQogICAgY29udGV4dC5sb2dfZGF0YXNldCgKICAgICAgICBrZXk9InNlbGVjdGVkX2ZlYXR1cmVzIiwKICAgICAgICBkZj1maW5hbF9kZiwKICAgICAgICBsb2NhbF9wYXRoPSJzZWxlY3RlZF9mZWF0dXJlcy5wYXJxdWV0IiwKICAgICAgICBmb3JtYXQ9InBhcnF1ZXQiLAogICAgKQoKICAgICMgQ3JlYXRpbmcgYSBuZXcgZmVhdHVyZSB2ZWN0b3IgY29udGFpbmluZyBvbmx5IHRoZSBpZGVudGlmaWVkIHRvcCBmZWF0dXJlcwogICAgaWYgaXNfZmVhdHVyZV92ZWN0b3IgYW5kIGRmX2FydGlmYWN0Lm1ldGEuc3BlYy5mZWF0dXJlcyBhbmQgb3V0cHV0X3ZlY3Rvcl9uYW1lOgogICAgICAgICMgU2VsZWN0aW5nIHRoZSB0b3AgSyBmZWF0dXJlcyBmcm9tIG91ciB0b3AgZmVhdHVyZSBkYXRhZnJhbWUKICAgICAgICBzZWxlY3RlZF9mZWF0dXJlcyA9IHJlc3VsdF9tYXRyaXhfZGYuaGVhZChrKS5pbmRleAoKICAgICAgICAjIE1hdGNoIHRoZSBzZWxlY3RlZCBmZWF0dXJlIG5hbWVzIHRvIHRoZSBGUyBGZWF0dXJlIGFubm90YXRpb25zCiAgICAgICAgbWF0Y2hlZF9zZWxlY3Rpb25zID0gWwogICAgICAgICAgICBmZWF0dXJlCiAgICAgICAgICAgIGZvciBmZWF0dXJlIGluIGxpc3QoZGZfYXJ0aWZhY3QubWV0YS5zcGVjLmZlYXR1cmVzKQogICAgICAgICAgICBmb3Igc2VsZWN0ZWQgaW4gbGlzdChzZWxlY3RlZF9mZWF0dXJlcykKICAgICAgICAgICAgaWYgZmVhdHVyZS5lbmRzd2l0aChzZWxlY3RlZCkKICAgICAgICBdCgogICAgICAgICMgRGVmaW5pbmcgb3VyIG5ldyBmZWF0dXJlIHZlY3RvcgogICAgICAgIHRvcF9mZWF0dXJlc19mdiA9IGZzLkZlYXR1cmVWZWN0b3IoCiAgICAgICAgICAgIG91dHB1dF92ZWN0b3JfbmFtZSwKICAgICAgICAgICAgbWF0Y2hlZF9zZWxlY3Rpb25zLAogICAgICAgICAgICBsYWJlbF9mZWF0dXJlPSJsYWJlbHMubGFiZWwiLAogICAgICAgICAgICBkZXNjcmlwdGlvbj0iZmVhdHVyZSB2ZWN0b3IgY29tcG9zZWQgc3RyaWN0bHkgb2Ygb3VyIHRvcCBmZWF0dXJlcyIsCiAgICAgICAgKQoKICAgICAgICAjIFNhdmluZwogICAgICAgIHRvcF9mZWF0dXJlc19mdi5zYXZlKCkKICAgICAgICBmcy5nZXRfb2ZmbGluZV9mZWF0dXJlcyh0b3BfZmVhdHVyZXNfZnYsIHRhcmdldD1QYXJxdWV0VGFyZ2V0KCkpCgogICAgICAgICMgTG9nZ2luZyBvdXIgbmV3IGZlYXR1cmUgdmVjdG9yIFVSSQogICAgICAgIGNvbnRleHQubG9nX3Jlc3VsdCgidG9wX2ZlYXR1cmVzX3ZlY3RvciIsIHRvcF9mZWF0dXJlc19mdi51cmkpCg== + functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IGpzb24KCmltcG9ydCBtbHJ1bgppbXBvcnQgbWxydW4uZGF0YXN0b3JlCmltcG9ydCBtbHJ1bi5mZWF0dXJlX3N0b3JlIGFzIGZzCmltcG9ydCBtbHJ1bi51dGlscwppbXBvcnQgbnVtcHkgYXMgbnAKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgcGxvdGx5LmV4cHJlc3MgYXMgcHgKZnJvbSBtbHJ1bi5hcnRpZmFjdHMgaW1wb3J0IFBsb3RseUFydGlmYWN0CmZyb20gbWxydW4uZGF0YXN0b3JlLnRhcmdldHMgaW1wb3J0IFBhcnF1ZXRUYXJnZXQKIyBNTFJ1biB1dGlscwpmcm9tIG1scnVuLnV0aWxzLmhlbHBlcnMgaW1wb3J0IGNyZWF0ZV9jbGFzcwojIEZlYXR1cmUgc2VsZWN0aW9uIHN0cmF0ZWdpZXMKZnJvbSBza2xlYXJuLmZlYXR1cmVfc2VsZWN0aW9uIGltcG9ydCBTZWxlY3RGcm9tTW9kZWwsIFNlbGVjdEtCZXN0CiMgU2NhbGUgZmVhdHVyZSBzY29yZXNnaXQgc3QKZnJvbSBza2xlYXJuLnByZXByb2Nlc3NpbmcgaW1wb3J0IE1pbk1heFNjYWxlcgojIFNLTGVhcm4gZXN0aW1hdG9ycyBsaXN0CmZyb20gc2tsZWFybi51dGlscyBpbXBvcnQgYWxsX2VzdGltYXRvcnMKCkRFRkFVTFRfU1RBVF9GSUxURVJTID0gWyJmX2NsYXNzaWYiLCAibXV0dWFsX2luZm9fY2xhc3NpZiIsICJjaGkyIiwgImZfcmVncmVzc2lvbiJdCkRFRkFVTFRfTU9ERUxfRklMVEVSUyA9IHsKICAgICJMaW5lYXJTVkMiOiAiTGluZWFyU1ZDIiwKICAgICJMb2dpc3RpY1JlZ3Jlc3Npb24iOiAiTG9naXN0aWNSZWdyZXNzaW9uIiwKICAgICJFeHRyYVRyZWVzQ2xhc3NpZmllciI6ICJFeHRyYVRyZWVzQ2xhc3NpZmllciIsCn0KCgpkZWYgc2hvd192YWx1ZXNfb25fYmFycyhheHMsIGhfdj0idiIsIHNwYWNlPTAuNCk6CiAgICBkZWYgX3Nob3dfb25fc2luZ2xlX3Bsb3QoYXhfKToKICAgICAgICBpZiBoX3YgPT0gInYiOgogICAgICAgICAgICBmb3IgcCBpbiBheF8ucGF0Y2hlczoKICAgICAgICAgICAgICAgIF94ID0gcC5nZXRfeCgpICsgcC5nZXRfd2lkdGgoKSAvIDIKICAgICAgICAgICAgICAgIF95ID0gcC5nZXRfeSgpICsgcC5nZXRfaGVpZ2h0KCkKICAgICAgICAgICAgICAgIHZhbHVlID0gaW50KHAuZ2V0X2hlaWdodCgpKQogICAgICAgICAgICAgICAgYXhfLnRleHQoX3gsIF95LCB2YWx1ZSwgaGE9ImNlbnRlciIpCiAgICAgICAgZWxpZiBoX3YgPT0gImgiOgogICAgICAgICAgICBmb3IgcCBpbiBheF8ucGF0Y2hlczoKICAgICAgICAgICAgICAgIF94ID0gcC5nZXRfeCgpICsgcC5nZXRfd2lkdGgoKSArIGZsb2F0KHNwYWNlKQogICAgICAgICAgICAgICAgX3kgPSBwLmdldF95KCkgKyBwLmdldF9oZWlnaHQoKQogICAgICAgICAgICAgICAgdmFsdWUgPSBpbnQocC5nZXRfd2lkdGgoKSkKICAgICAgICAgICAgICAgIGF4Xy50ZXh0KF94LCBfeSwgdmFsdWUsIGhhPSJsZWZ0IikKCiAgICBpZiBpc2luc3RhbmNlKGF4cywgbnAubmRhcnJheSk6CiAgICAgICAgZm9yIGlkeCwgYXggaW4gbnAubmRlbnVtZXJhdGUoYXhzKToKICAgICAgICAgICAgX3Nob3dfb25fc2luZ2xlX3Bsb3QoYXgpCiAgICBlbHNlOgogICAgICAgIF9zaG93X29uX3NpbmdsZV9wbG90KGF4cykKCgpkZWYgcGxvdF9zdGF0KGNvbnRleHQsIHN0YXRfbmFtZSwgc3RhdF9kZik6CiAgICBzb3J0ZWRfZGYgPSBzdGF0X2RmLnNvcnRfdmFsdWVzKHN0YXRfbmFtZSkKICAgIGZpZyA9IHB4LmJhcigKICAgICAgICBkYXRhX2ZyYW1lPXNvcnRlZF9kZiwKICAgICAgICB4PXN0YXRfbmFtZSwKICAgICAgICB5PXNvcnRlZF9kZi5pbmRleCwKICAgICAgICB0aXRsZT1mIntzdGF0X25hbWV9IGZlYXR1cmUgc2NvcmVzIiwKICAgICAgICBjb2xvcj1zdGF0X25hbWUsCiAgICApCiAgICBjb250ZXh0LmxvZ19hcnRpZmFjdCgKICAgICAgICBpdGVtPVBsb3RseUFydGlmYWN0KGtleT1zdGF0X25hbWUsIGZpZ3VyZT1maWcpLAogICAgICAgIGxvY2FsX3BhdGg9ZiJ7c3RhdF9uYW1lfS5odG1sIiwKICAgICkKCgpkZWYgZmVhdHVyZV9zZWxlY3Rpb24oCiAgICBjb250ZXh0LAogICAgZGZfYXJ0aWZhY3QsCiAgICBrOiBpbnQgPSA1LAogICAgbWluX3ZvdGVzOiBmbG9hdCA9IDAuNSwKICAgIGxhYmVsX2NvbHVtbjogc3RyID0gTm9uZSwKICAgIHN0YXRfZmlsdGVyczogbGlzdCA9IE5vbmUsCiAgICBtb2RlbF9maWx0ZXJzOiBkaWN0ID0gTm9uZSwKICAgIG1heF9zY2FsZWRfc2NvcmVzOiBib29sID0gVHJ1ZSwKICAgIHNhbXBsZV9yYXRpbzogZmxvYXQgPSBOb25lLAogICAgb3V0cHV0X3ZlY3Rvcl9uYW1lOiBmbG9hdCA9IE5vbmUsCiAgICBpZ25vcmVfdHlwZV9lcnJvcnM6IGJvb2wgPSBGYWxzZSwKKToKICAgICIiIgogICAgQXBwbGllcyBzZWxlY3RlZCBmZWF0dXJlIHNlbGVjdGlvbiBzdGF0aXN0aWNhbCBmdW5jdGlvbnMgb3IgbW9kZWxzIG9uIG91ciAnZGZfYXJ0aWZhY3QnLgoKICAgIEVhY2ggc3RhdGlzdGljYWwgZnVuY3Rpb24gb3IgbW9kZWwgd2lsbCB2b3RlIGZvciBpdCdzIGJlc3QgSyBzZWxlY3RlZCBmZWF0dXJlcy4KICAgIElmIGEgZmVhdHVyZSBoYXMgPj0gJ21pbl92b3Rlcycgdm90ZXMsIGl0IHdpbGwgYmUgc2VsZWN0ZWQuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgIHRoZSBmdW5jdGlvbiBjb250ZXh0LgogICAgOnBhcmFtIGRmX2FydGlmYWN0OiAgICAgICAgIGRhdGFmcmFtZSB0byBwYXNzIGFzIGlucHV0LgogICAgOnBhcmFtIGs6ICAgICAgICAgICAgICAgICAgIG51bWJlciBvZiB0b3AgZmVhdHVyZXMgdG8gc2VsZWN0IGZyb20gZWFjaCBzdGF0aXN0aWNhbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uIG9yIG1vZGVsLgogICAgOnBhcmFtIG1pbl92b3RlczogICAgICAgICAgIG1pbmltYWwgbnVtYmVyIG9mIHZvdGVzIChmcm9tIGEgbW9kZWwgb3IgYnkgc3RhdGlzdGljYWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbikgbmVlZGVkIGZvciBhIGZlYXR1cmUgdG8gYmUgc2VsZWN0ZWQuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2FuIGJlIHNwZWNpZmllZCBieSBwZXJjZW50YWdlIG9mIHZvdGVzIG9yIGFic29sdXRlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtYmVyIG9mIHZvdGVzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbjogICAgICAgIGdyb3VuZC10cnV0aCAoeSkgbGFiZWxzLgogICAgOnBhcmFtIHN0YXRfZmlsdGVyczogICAgICAgIHN0YXRpc3RpY2FsIGZ1bmN0aW9ucyB0byBhcHBseSB0byB0aGUgZmVhdHVyZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoZnJvbSBza2xlYXJuLmZlYXR1cmVfc2VsZWN0aW9uKS4KICAgIDpwYXJhbSBtb2RlbF9maWx0ZXJzOiAgICAgICBtb2RlbHMgdG8gdXNlIGZvciBmZWF0dXJlIGV2YWx1YXRpb24sIGNhbiBiZSBzcGVjaWZpZWQgYnkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBuYW1lIChleC4gTGluZWFyU1ZDKSwgZm9ybWFsaXplZCBqc29uIChjb250YWlucyAnQ0xBU1MnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdGSVQnLCAnTUVUQScpIG9yIGEgcGF0aCB0byBzdWNoIGpzb24gZmlsZS4KICAgIDpwYXJhbSBtYXhfc2NhbGVkX3Njb3JlczogICBwcm9kdWNlIGZlYXR1cmUgc2NvcmVzIHRhYmxlIHNjYWxlZCB3aXRoIG1heF9zY2FsZXIuCiAgICA6cGFyYW0gc2FtcGxlX3JhdGlvOiAgICAgICAgcGVyY2VudGFnZSBvZiB0aGUgZGF0YXNldCB0aGUgdXNlciB3aXNoZXMgdG8gY29tcHV0ZSB0aGUgZmVhdHVyZSBzZWxlY3Rpb24gcHJvY2VzcyBvbi4KICAgIDpwYXJhbSBvdXRwdXRfdmVjdG9yX25hbWU6ICBjcmVhdGVzIGEgbmV3IGZlYXR1cmUgdmVjdG9yIGNvbnRhaW5pbmcgb25seSB0aGUgaWRlbnRpZmllcyBmZWF0dXJlcy4KICAgIDpwYXJhbSBpZ25vcmVfdHlwZV9lcnJvcnM6ICBza2lwcyBkYXRhdHlwZXMgdGhhdCBhcmUgbmVpdGhlciBmbG9hdCBub3IgaW50IHdpdGhpbiB0aGUgZmVhdHVyZSB2ZWN0b3IuCiAgICAiIiIKICAgIHN0YXRfZmlsdGVycyA9IHN0YXRfZmlsdGVycyBvciBERUZBVUxUX1NUQVRfRklMVEVSUwogICAgbW9kZWxfZmlsdGVycyA9IG1vZGVsX2ZpbHRlcnMgb3IgREVGQVVMVF9NT0RFTF9GSUxURVJTCiAgICAjIENoZWNrIGlmIGRmLm1ldGEgaXMgdmFsaWQsIGlmIGl0IGlzLCBsb29rIGZvciBhIGZlYXR1cmUgdmVjdG9yCiAgICBzdG9yZV91cmlfcHJlZml4LCBfID0gbWxydW4uZGF0YXN0b3JlLnBhcnNlX3N0b3JlX3VyaShkZl9hcnRpZmFjdC5hcnRpZmFjdF91cmwpCiAgICBpc19mZWF0dXJlX3ZlY3RvciA9IG1scnVuLnV0aWxzLlN0b3JlUHJlZml4LkZlYXR1cmVWZWN0b3IgPT0gc3RvcmVfdXJpX3ByZWZpeAoKICAgICMgTG9vayBpbnNpZGUgbWV0YS5zcGVjLmxhYmVsX2ZlYXR1cmUgdG8gaWRlbnRpZnkgdGhlIGxhYmVsX2NvbHVtbiBpZiB0aGUgdXNlciBkaWQgbm90IHNwZWNpZnkgaXQKICAgIGlmIGxhYmVsX2NvbHVtbiBpcyBOb25lOgogICAgICAgIGlmIGlzX2ZlYXR1cmVfdmVjdG9yOgogICAgICAgICAgICBsYWJlbF9jb2x1bW4gPSBkZl9hcnRpZmFjdC5tZXRhLnNwZWMubGFiZWxfZmVhdHVyZS5zcGxpdCgiLiIpWzFdCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigiTm8gbGFiZWxfY29sdW1uIHdhcyBnaXZlbiwgcGxlYXNlIGFkZCBhIGxhYmVsX2NvbHVtbi4iKQoKICAgICMgVXNlIHRoZSBmZWF0dXJlIHZlY3RvciBhcyBkYXRhZnJhbWUKICAgIGRmID0gZGZfYXJ0aWZhY3QuYXNfZGYoKQoKICAgICMgRW5zdXJlIGsgaXMgbm90IGJpZ2dlciB0aGFuIHRoZSB0b3RhbCBudW1iZXIgb2YgZmVhdHVyZXMKICAgIGlmIGsgPiBkZi5zaGFwZVsxXToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIksgY2Fubm90IGJlIGJpZ2dlciB0aGFuIHRoZSB0b3RhbCBudW1iZXIgb2YgZmVhdHVyZXMgKHtkZi5zaGFwZVsxXX0pLiBQbGVhc2UgY2hvb3NlIGEgc21hbGxlciBLLiIKICAgICAgICApCiAgICBlbGlmIGsgPCAxOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIksgY2Fubm90IGJlIHNtYWxsZXIgdGhhbiAxLiBQbGVhc2UgY2hvb3NlIGEgYmlnZ2VyIEsuIikKCiAgICAjIENyZWF0ZSBhIHNhbXBsZSBkYXRhZnJhbWUgb2YgdGhlIG9yaWdpbmFsIGZlYXR1cmUgdmVjdG9yCiAgICBpZiBzYW1wbGVfcmF0aW86CiAgICAgICAgZGYgPSAoCiAgICAgICAgICAgIGRmLmdyb3VwYnkobGFiZWxfY29sdW1uKQogICAgICAgICAgICAuYXBwbHkobGFtYmRhIHg6IHguc2FtcGxlKGZyYWM9c2FtcGxlX3JhdGlvKSkKICAgICAgICAgICAgLnJlc2V0X2luZGV4KGRyb3A9VHJ1ZSkKICAgICAgICApCiAgICAgICAgZGYgPSBkZi5kcm9wbmEoKQoKICAgICMgU2V0IGZlYXR1cmUgdmVjdG9yIGFuZCBsYWJlbHMKICAgIHkgPSBkZi5wb3AobGFiZWxfY29sdW1uKQogICAgWCA9IGRmCgogICAgaWYgbnAub2JqZWN0XyBpbiBsaXN0KFguZHR5cGVzKSBhbmQgaWdub3JlX3R5cGVfZXJyb3JzIGlzIEZhbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYie2RmLnNlbGVjdF9kdHlwZXMoaW5jbHVkZT1bJ29iamVjdCddKS5jb2x1bW5zLnRvbGlzdCgpfSBhcmUgbmVpdGhlciBmbG9hdCBvciBpbnQuIgogICAgICAgICkKCiAgICAjIENyZWF0ZSBzZWxlY3RlZCBzdGF0aXN0aWNhbCBlc3RpbWF0b3JzCiAgICBzdGF0X2Z1bmN0aW9uc19saXN0ID0gewogICAgICAgIHN0YXRfbmFtZTogU2VsZWN0S0Jlc3QoCiAgICAgICAgICAgIHNjb3JlX2Z1bmM9Y3JlYXRlX2NsYXNzKGYic2tsZWFybi5mZWF0dXJlX3NlbGVjdGlvbi57c3RhdF9uYW1lfSIpLCBrPWsKICAgICAgICApCiAgICAgICAgZm9yIHN0YXRfbmFtZSBpbiBzdGF0X2ZpbHRlcnMKICAgIH0KICAgIHJlcXVpcmVzX2FicyA9IFsiY2hpMiJdCgogICAgIyBSdW4gc3RhdGlzdGljIGZpbHRlcnMKICAgIHNlbGVjdGVkX2ZlYXR1cmVzX2FnZyA9IHt9CiAgICBzdGF0c19kZiA9IHBkLkRhdGFGcmFtZShpbmRleD1YLmNvbHVtbnMpLmRyb3BuYSgpCgogICAgZm9yIHN0YXRfbmFtZSwgc3RhdF9mdW5jIGluIHN0YXRfZnVuY3Rpb25zX2xpc3QuaXRlbXMoKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHBhcmFtcyA9IChYLCB5KSBpZiBzdGF0X25hbWUgaW4gcmVxdWlyZXNfYWJzIGVsc2UgKGFicyhYKSwgeSkKICAgICAgICAgICAgc3RhdCA9IHN0YXRfZnVuYy5maXQoKnBhcmFtcykKCiAgICAgICAgICAgICMgQ29sbGVjdCBzdGF0IGZ1bmN0aW9uIHJlc3VsdHMKICAgICAgICAgICAgc3RhdF9kZiA9IHBkLkRhdGFGcmFtZSgKICAgICAgICAgICAgICAgIGluZGV4PVguY29sdW1ucywgY29sdW1ucz1bc3RhdF9uYW1lXSwgZGF0YT1zdGF0LnNjb3Jlc18KICAgICAgICAgICAgKQogICAgICAgICAgICBwbG90X3N0YXQoY29udGV4dCwgc3RhdF9uYW1lLCBzdGF0X2RmKQogICAgICAgICAgICBzdGF0c19kZiA9IHN0YXRzX2RmLmpvaW4oc3RhdF9kZikKCiAgICAgICAgICAgICMgU2VsZWN0IEsgQmVzdCBmZWF0dXJlcwogICAgICAgICAgICBzZWxlY3RlZF9mZWF0dXJlcyA9IFguY29sdW1uc1tzdGF0X2Z1bmMuZ2V0X3N1cHBvcnQoKV0KICAgICAgICAgICAgc2VsZWN0ZWRfZmVhdHVyZXNfYWdnW3N0YXRfbmFtZV0gPSBzZWxlY3RlZF9mZWF0dXJlcwoKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDb3VsZG4ndCBjYWxjdWxhdGUge3N0YXRfbmFtZX0gYmVjYXVzZSBvZjoge2V9IikKCiAgICAjIENyZWF0ZSBtb2RlbHMgZnJvbSBjbGFzcyBuYW1lIC8ganNvbiBmaWxlIC8ganNvbiBwYXJhbXMKICAgIGFsbF9za2xlYXJuX2VzdGltYXRvcnMgPSBkaWN0KGFsbF9lc3RpbWF0b3JzKCkpIGlmIGxlbihtb2RlbF9maWx0ZXJzKSA+IDAgZWxzZSB7fQogICAgc2VsZWN0ZWRfbW9kZWxzID0ge30KICAgIGZvciBtb2RlbF9uYW1lLCBtb2RlbCBpbiBtb2RlbF9maWx0ZXJzLml0ZW1zKCk6CiAgICAgICAgaWYgIi5qc29uIiBpbiBtb2RlbDoKICAgICAgICAgICAgY3VycmVudF9tb2RlbCA9IGpzb24ubG9hZChvcGVuKG1vZGVsLCAiciIpKQogICAgICAgICAgICBjbGFzc2lmaWVyX2NsYXNzID0gY3JlYXRlX2NsYXNzKGN1cnJlbnRfbW9kZWxbIk1FVEEiXVsiY2xhc3MiXSkKICAgICAgICAgICAgc2VsZWN0ZWRfbW9kZWxzW21vZGVsX25hbWVdID0gY2xhc3NpZmllcl9jbGFzcygqKmN1cnJlbnRfbW9kZWxbIkNMQVNTIl0pCiAgICAgICAgZWxpZiBtb2RlbCBpbiBhbGxfc2tsZWFybl9lc3RpbWF0b3JzOgogICAgICAgICAgICBzZWxlY3RlZF9tb2RlbHNbbW9kZWxfbmFtZV0gPSBhbGxfc2tsZWFybl9lc3RpbWF0b3JzW21vZGVsX25hbWVdKCkKCiAgICAgICAgZWxzZToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgY3VycmVudF9tb2RlbCA9IGpzb24ubG9hZHMobW9kZWwpCiAgICAgICAgICAgICAgICBjbGFzc2lmaWVyX2NsYXNzID0gY3JlYXRlX2NsYXNzKGN1cnJlbnRfbW9kZWxbIk1FVEEiXVsiY2xhc3MiXSkKICAgICAgICAgICAgICAgIHNlbGVjdGVkX21vZGVsc1ttb2RlbF9uYW1lXSA9IGNsYXNzaWZpZXJfY2xhc3MoKipjdXJyZW50X21vZGVsWyJDTEFTUyJdKQogICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYidW5hYmxlIHRvIGxvYWQge21vZGVsfSBiZWNhdXNlIG9mOiB7ZX0iKQoKICAgICMgUnVuIG1vZGVsIGZpbHRlcnMKICAgIG1vZGVsc19kZiA9IHBkLkRhdGFGcmFtZShpbmRleD1YLmNvbHVtbnMpCiAgICBmb3IgbW9kZWxfbmFtZSwgbW9kZWwgaW4gc2VsZWN0ZWRfbW9kZWxzLml0ZW1zKCk6CgogICAgICAgIGlmIG1vZGVsX25hbWUgPT0gIkxvZ2lzdGljUmVncmVzc2lvbiI6CiAgICAgICAgICAgIG1vZGVsLnNldF9wYXJhbXMoc29sdmVyPSJsaWJsaW5lYXIiKQoKICAgICAgICAjIFRyYWluIG1vZGVsIGFuZCBnZXQgZmVhdHVyZSBpbXBvcnRhbmNlCiAgICAgICAgc2VsZWN0X2Zyb21fbW9kZWwgPSBTZWxlY3RGcm9tTW9kZWwobW9kZWwpLmZpdChYLCB5KQogICAgICAgIGZlYXR1cmVfaWR4ID0gc2VsZWN0X2Zyb21fbW9kZWwuZ2V0X3N1cHBvcnQoKQogICAgICAgIGZlYXR1cmVfbmFtZXMgPSBYLmNvbHVtbnNbZmVhdHVyZV9pZHhdCiAgICAgICAgc2VsZWN0ZWRfZmVhdHVyZXNfYWdnW21vZGVsX25hbWVdID0gZmVhdHVyZV9uYW1lcy50b2xpc3QoKQoKICAgICAgICAjIENvbGxlY3QgbW9kZWwgZmVhdHVyZSBpbXBvcnRhbmNlCiAgICAgICAgaWYgaGFzYXR0cihzZWxlY3RfZnJvbV9tb2RlbC5lc3RpbWF0b3JfLCAiY29lZl8iKToKICAgICAgICAgICAgc3RhdF9kZiA9IHNlbGVjdF9mcm9tX21vZGVsLmVzdGltYXRvcl8uY29lZl8KICAgICAgICBlbGlmIGhhc2F0dHIoc2VsZWN0X2Zyb21fbW9kZWwuZXN0aW1hdG9yXywgImZlYXR1cmVfaW1wb3J0YW5jZXNfIik6CiAgICAgICAgICAgIHN0YXRfZGYgPSBzZWxlY3RfZnJvbV9tb2RlbC5lc3RpbWF0b3JfLmZlYXR1cmVfaW1wb3J0YW5jZXNfCgogICAgICAgIHN0YXRfZGYgPSBwZC5EYXRhRnJhbWUoaW5kZXg9WC5jb2x1bW5zLCBjb2x1bW5zPVttb2RlbF9uYW1lXSwgZGF0YT1zdGF0X2RmWzBdKQogICAgICAgIG1vZGVsc19kZiA9IG1vZGVsc19kZi5qb2luKHN0YXRfZGYpCgogICAgICAgIHBsb3Rfc3RhdChjb250ZXh0LCBtb2RlbF9uYW1lLCBzdGF0X2RmKQoKICAgICMgQ3JlYXRlIGZlYXR1cmVfc2NvcmVzIERGIHdpdGggc3RhdCAmIG1vZGVsIGZpbHRlcnMgc2NvcmVzCiAgICByZXN1bHRfbWF0cml4X2RmID0gcGQuY29uY2F0KFtzdGF0c19kZiwgbW9kZWxzX2RmXSwgYXhpcz0xLCBzb3J0PUZhbHNlKQogICAgY29udGV4dC5sb2dfZGF0YXNldCgKICAgICAgICBrZXk9ImZlYXR1cmVfc2NvcmVzIiwKICAgICAgICBkZj1yZXN1bHRfbWF0cml4X2RmLAogICAgICAgIGxvY2FsX3BhdGg9ImZlYXR1cmVfc2NvcmVzLnBhcnF1ZXQiLAogICAgICAgIGZvcm1hdD0icGFycXVldCIsCiAgICApCiAgICBpZiBtYXhfc2NhbGVkX3Njb3JlczoKICAgICAgICBub3JtYWxpemVkX2RmID0gcmVzdWx0X21hdHJpeF9kZi5yZXBsYWNlKFtucC5pbmYsIC1ucC5pbmZdLCBucC5uYW4pLnZhbHVlcwogICAgICAgIG1pbl9tYXhfc2NhbGVyID0gTWluTWF4U2NhbGVyKCkKICAgICAgICBub3JtYWxpemVkX2RmID0gbWluX21heF9zY2FsZXIuZml0X3RyYW5zZm9ybShub3JtYWxpemVkX2RmKQogICAgICAgIG5vcm1hbGl6ZWRfZGYgPSBwZC5EYXRhRnJhbWUoCiAgICAgICAgICAgIGRhdGE9bm9ybWFsaXplZF9kZiwKICAgICAgICAgICAgY29sdW1ucz1yZXN1bHRfbWF0cml4X2RmLmNvbHVtbnMsCiAgICAgICAgICAgIGluZGV4PXJlc3VsdF9tYXRyaXhfZGYuaW5kZXgsCiAgICAgICAgKQogICAgICAgIGNvbnRleHQubG9nX2RhdGFzZXQoCiAgICAgICAgICAgIGtleT0ibWF4X3NjYWxlZF9zY29yZXNfZmVhdHVyZV9zY29yZXMiLAogICAgICAgICAgICBkZj1ub3JtYWxpemVkX2RmLAogICAgICAgICAgICBsb2NhbF9wYXRoPSJtYXhfc2NhbGVkX3Njb3Jlc19mZWF0dXJlX3Njb3Jlcy5wYXJxdWV0IiwKICAgICAgICAgICAgZm9ybWF0PSJwYXJxdWV0IiwKICAgICAgICApCgogICAgIyBDcmVhdGUgZmVhdHVyZSBjb3VudCBEYXRhRnJhbWUKICAgIGZvciB0ZXN0X25hbWUgaW4gc2VsZWN0ZWRfZmVhdHVyZXNfYWdnOgogICAgICAgIHJlc3VsdF9tYXRyaXhfZGZbdGVzdF9uYW1lXSA9IFsKICAgICAgICAgICAgMSBpZiB4IGluIHNlbGVjdGVkX2ZlYXR1cmVzX2FnZ1t0ZXN0X25hbWVdIGVsc2UgMCBmb3IgeCBpbiBYLmNvbHVtbnMKICAgICAgICBdCiAgICByZXN1bHRfbWF0cml4X2RmLmxvY1s6LCAibnVtX3ZvdGVzIl0gPSByZXN1bHRfbWF0cml4X2RmLnN1bShheGlzPTEpCiAgICBjb250ZXh0LmxvZ19kYXRhc2V0KAogICAgICAgIGtleT0ic2VsZWN0ZWRfZmVhdHVyZXNfY291bnQiLAogICAgICAgIGRmPXJlc3VsdF9tYXRyaXhfZGYsCiAgICAgICAgbG9jYWxfcGF0aD0ic2VsZWN0ZWRfZmVhdHVyZXNfY291bnQucGFycXVldCIsCiAgICAgICAgZm9ybWF0PSJwYXJxdWV0IiwKICAgICkKCiAgICAjIEhvdyBtYW55IHZvdGVzIGFyZSBuZWVkZWQgZm9yIGEgZmVhdHVyZSB0byBiZSBzZWxlY3RlZD8KICAgIGlmIGlzaW5zdGFuY2UobWluX3ZvdGVzLCBpbnQpOgogICAgICAgIHZvdGVzX25lZWRlZCA9IG1pbl92b3RlcwogICAgZWxzZToKICAgICAgICBudW1fZmlsdGVycyA9IGxlbihzdGF0X2ZpbHRlcnMpICsgbGVuKG1vZGVsX2ZpbHRlcnMpCiAgICAgICAgdm90ZXNfbmVlZGVkID0gaW50KG5wLmZsb29yKG51bV9maWx0ZXJzICogbWF4KG1pbihtaW5fdm90ZXMsIDEpLCAwKSkpCiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYidm90ZXMgbmVlZGVkIHRvIGJlIHNlbGVjdGVkOiB7dm90ZXNfbmVlZGVkfSIpCgogICAgIyBDcmVhdGUgZmluYWwgZmVhdHVyZSBkYXRhZnJhbWUKICAgIHNlbGVjdGVkX2ZlYXR1cmVzID0gcmVzdWx0X21hdHJpeF9kZlsKICAgICAgICByZXN1bHRfbWF0cml4X2RmLm51bV92b3RlcyA+PSB2b3Rlc19uZWVkZWQKICAgIF0uaW5kZXgudG9saXN0KCkKICAgIGdvb2RfZmVhdHVyZV9kZiA9IGRmLmxvY1s6LCBzZWxlY3RlZF9mZWF0dXJlc10KICAgIGZpbmFsX2RmID0gcGQuY29uY2F0KFtnb29kX2ZlYXR1cmVfZGYsIHldLCBheGlzPTEpCiAgICBjb250ZXh0LmxvZ19kYXRhc2V0KAogICAgICAgIGtleT0ic2VsZWN0ZWRfZmVhdHVyZXMiLAogICAgICAgIGRmPWZpbmFsX2RmLAogICAgICAgIGxvY2FsX3BhdGg9InNlbGVjdGVkX2ZlYXR1cmVzLnBhcnF1ZXQiLAogICAgICAgIGZvcm1hdD0icGFycXVldCIsCiAgICApCgogICAgIyBDcmVhdGluZyBhIG5ldyBmZWF0dXJlIHZlY3RvciBjb250YWluaW5nIG9ubHkgdGhlIGlkZW50aWZpZWQgdG9wIGZlYXR1cmVzCiAgICBpZiBpc19mZWF0dXJlX3ZlY3RvciBhbmQgZGZfYXJ0aWZhY3QubWV0YS5zcGVjLmZlYXR1cmVzIGFuZCBvdXRwdXRfdmVjdG9yX25hbWU6CiAgICAgICAgIyBTZWxlY3RpbmcgdGhlIHRvcCBLIGZlYXR1cmVzIGZyb20gb3VyIHRvcCBmZWF0dXJlIGRhdGFmcmFtZQogICAgICAgIHNlbGVjdGVkX2ZlYXR1cmVzID0gcmVzdWx0X21hdHJpeF9kZi5oZWFkKGspLmluZGV4CgogICAgICAgICMgTWF0Y2ggdGhlIHNlbGVjdGVkIGZlYXR1cmUgbmFtZXMgdG8gdGhlIEZTIEZlYXR1cmUgYW5ub3RhdGlvbnMKICAgICAgICBtYXRjaGVkX3NlbGVjdGlvbnMgPSBbCiAgICAgICAgICAgIGZlYXR1cmUKICAgICAgICAgICAgZm9yIGZlYXR1cmUgaW4gbGlzdChkZl9hcnRpZmFjdC5tZXRhLnNwZWMuZmVhdHVyZXMpCiAgICAgICAgICAgIGZvciBzZWxlY3RlZCBpbiBsaXN0KHNlbGVjdGVkX2ZlYXR1cmVzKQogICAgICAgICAgICBpZiBmZWF0dXJlLmVuZHN3aXRoKHNlbGVjdGVkKQogICAgICAgIF0KCiAgICAgICAgIyBEZWZpbmluZyBvdXIgbmV3IGZlYXR1cmUgdmVjdG9yCiAgICAgICAgdG9wX2ZlYXR1cmVzX2Z2ID0gZnMuRmVhdHVyZVZlY3RvcigKICAgICAgICAgICAgb3V0cHV0X3ZlY3Rvcl9uYW1lLAogICAgICAgICAgICBtYXRjaGVkX3NlbGVjdGlvbnMsCiAgICAgICAgICAgIGxhYmVsX2ZlYXR1cmU9ImxhYmVscy5sYWJlbCIsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJmZWF0dXJlIHZlY3RvciBjb21wb3NlZCBzdHJpY3RseSBvZiBvdXIgdG9wIGZlYXR1cmVzIiwKICAgICAgICApCgogICAgICAgICMgU2F2aW5nCiAgICAgICAgdG9wX2ZlYXR1cmVzX2Z2LnNhdmUoKQogICAgICAgIGZzLmdldF9vZmZsaW5lX2ZlYXR1cmVzKHRvcF9mZWF0dXJlc19mdiwgdGFyZ2V0PVBhcnF1ZXRUYXJnZXQoKSkKCiAgICAgICAgIyBMb2dnaW5nIG91ciBuZXcgZmVhdHVyZSB2ZWN0b3IgVVJJCiAgICAgICAgY29udGV4dC5sb2dfcmVzdWx0KCJ0b3BfZmVhdHVyZXNfdmVjdG9yIiwgdG9wX2ZlYXR1cmVzX2Z2LnVyaSkK commands: [] code_origin: '' origin_filename: '' @@ -30,7 +30,7 @@ spec: - name: space default: 0.4 outputs: [] - lineno: 54 + lineno: 43 has_varargs: false has_kwargs: false plot_stat: @@ -41,7 +41,7 @@ spec: - name: stat_name - name: stat_df outputs: [] - lineno: 76 + lineno: 65 has_varargs: false has_kwargs: false feature_selection: @@ -88,7 +88,7 @@ spec: default: true - name: sample_ratio type: float - doc: percentage of the dataset the user whishes to compute the feature selection + doc: percentage of the dataset the user wishes to compute the feature selection process on. default: null - name: output_vector_name @@ -99,18 +99,13 @@ spec: type: bool doc: skips datatypes that are neither float nor int within the feature vector. default: false - - name: is_feature_vector - type: bool - doc: bool stating if the data is passed as a feature vector. - default: false outputs: [] - lineno: 106 + lineno: 80 has_varargs: false has_kwargs: false description: Select features through multiple Statistical and Model filters default_handler: feature_selection disable_auto_mount: false - clone_target_dir: '' env: [] priority_class_name: '' preemption_mode: prevent diff --git a/feature_selection/item.yaml b/feature_selection/item.yaml index 7e80a417b..ced618e00 100644 --- a/feature_selection/item.yaml +++ b/feature_selection/item.yaml @@ -12,7 +12,7 @@ labels: author: orz maintainers: [] marketplaceType: '' -mlrunVersion: 1.1.0 +mlrunVersion: 1.6.3 name: feature-selection platformVersion: 3.5.0 spec: @@ -22,4 +22,4 @@ spec: kind: job requirements: [] url: '' -version: 1.4.0 +version: 1.5.0 diff --git a/feature_selection/requirements.txt b/feature_selection/requirements.txt index 961f64ea4..a13fc8ce6 100644 --- a/feature_selection/requirements.txt +++ b/feature_selection/requirements.txt @@ -1,5 +1,3 @@ scikit-learn~=1.0.2 -matplotlib -seaborn scikit-plot - +plotly~=5.4.0 diff --git a/feature_selection/test_feature_selection.py b/feature_selection/test_feature_selection.py index 6289648f2..3032b3193 100644 --- a/feature_selection/test_feature_selection.py +++ b/feature_selection/test_feature_selection.py @@ -12,14 +12,31 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from mlrun import code_to_function -from pathlib import Path +import os import shutil +from pathlib import Path -METRICS_PATH = 'data/metrics.pq' -ARTIFACTS_PATH = 'artifacts' -RUNS_PATH = 'runs' -SCHEDULES_PATH = 'schedules' +import mlrun + +METRICS_PATH = "data/metrics.pq" +ARTIFACTS_PATH = "artifacts" +RUNS_PATH = "runs" +SCHEDULES_PATH = "schedules" +PLOTS_PATH = os.path.abspath("./artifacts/feature-selection-feature-selection/0") + + +def _validate_paths(paths): + """ + Check if all the expected plot are saved + """ + base_folder = PLOTS_PATH + for path in paths: + full_path = os.path.join(base_folder, path) + if Path(full_path).is_file(): + print(f"{path} exist") + else: + raise FileNotFoundError(f"{path} not found!") + return True def _delete_outputs(paths): @@ -29,20 +46,23 @@ def _delete_outputs(paths): def test_run_local_feature_selection(): - fn = code_to_function(name='test_run_local_feature_selection', - filename="feature_selection.py", - handler="feature_selection", - kind="local", - ) - fn.spec.command = "feature_selection.py" + fn = mlrun.import_function("function.yaml") run = fn.run( params={ - 'k': 2, - 'min_votes': 0.3, - 'label_column': 'is_error', + "k": 2, + "min_votes": 0.3, + "label_column": "is_error", }, - inputs={'df_artifact': 'data/metrics.pq'}, - artifact_path='artifacts/', + inputs={"df_artifact": "data/metrics.pq"}, + artifact_path="artifacts/", + local=True, + ) + assert _validate_paths( + [ + "chi2.html", + "f_classif.html", + "f_regression.html", + "mutual_info_classif.html", + ] ) - assert run.artifact('feature_scores').get() and run.artifact('selected_features').get() _delete_outputs({ARTIFACTS_PATH, RUNS_PATH, SCHEDULES_PATH})