import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from scipy.interpolate import UnivariateSpline
import warnings
warnings.filterwarnings('ignore')
 
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
 
class SulfosalicylateIronExperiment:
    def __init__(self):
        self.fe_concentration = 0.001
        self.ligand_concentration = 0.001
        self.hclo4_concentration = 0.01
        self.hclo4_volume = 10.0
        self.total_mixing_volume = 10.0
        self.total_volume = self.hclo4_volume + self.total_mixing_volume
        self.mole_fractions = np.array([0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])
        self.absorbances = None
        self.absorbances_all = None
 
    def input_data(self):
        print("=== 磺基水杨酸铁配位数和稳定常数测定实验数据处理 ===")
        print("\n请输入11个数据点的吸光度值 (平行实验):")
        print("格式: 点1实验1 点1实验2 点2实验1 点2实验2 ... 点11实验1 点11实验2")
 
        data_str = input("请输入22个吸光度值: ")
        data = list(map(float, data_str.split()))
 
        if len(data) != 22:
            raise ValueError
("必须输入22个吸光度值!")  
        self.absorbances_all = np.array(data).reshape(11, 2)
        self.absorbances = np.mean(self.absorbances_all, axis=1)
        self.absorbances_std = np.std(self.absorbances_all, axis=1, ddof=1)
 
        print("\n数据输入完成!")
        print(f"摩尔分数: {self.mole_fractions}")
        print(f"平均吸光度: {self.absorbances}")
 
        return self.mole_fractions, self.absorbances
 
    def linear_fit(self, x_data, y_data):
        A = np.vstack([x_data, np.ones(len(x_data))]).T
        m, c = np.linalg.lstsq(A, y_data, rcond=None)[0]
        return m, c
 
    def calculate_intersection(self, m1, c1, m2, c2):
        x_intersect = (c2 - c1) / (m1 - m2)
        y_intersect = m1 * x_intersect + c1
        return x_intersect, y_intersect
 
    def find_curve_intersection(self, x_intersect):
        for i in range(len(self.mole_fractions)-1):
            if self.mole_fractions[i] <= x_intersect <= self.mole_fractions[i+1]:
                x1, x2 = self.mole_fractions[i], self.mole_fractions[i+1]
                y1, y2 = self.absorbances[i], self.absorbances[i+1]
                y_curve = y1 + (y2 - y1) * (x_intersect - x1) / (x2 - x1)
                return y_curve
        return self.absorbances[np.argmax(self.absorbances)]
 
    def calculate_acid_effect(self, pH=2.5):
        H_plus = 10**(-pH)
        K2 = 10**(-2.6)
        K3 = 10**(-11.7)
 
        alpha = 1 + H_plus/K3 + H_plus**2/(K2*K3)
        lg_alpha 
= np.
log10(alpha
) 
        return lg_alpha
 
    def analyze_data(self):
        if self.absorbances is None:
            raise ValueError
("请先输入数据!")  
        left_indices = [0, 1, 2]
        right_indices = [8, 9, 10]
 
        m_left, c_left = self.linear_fit(
            self.mole_fractions[left_indices], 
            self.absorbances[left_indices]
        )
 
        m_right, c_right = self.linear_fit(
            self.mole_fractions[right_indices], 
            self.absorbances[right_indices]
        )
 
        x_intersect, D1 = self.calculate_intersection(m_left, c_left, m_right, c_right)
 
        D2 = self.find_curve_intersection(x_intersect)
 
        coordination_number = x_intersect / (1 - x_intersect)
 
        if D1 > 0:
            dissociation_degree = (D1 - D2) / D1
        else:
            dissociation_degree = 0
 
        fe_initial_moles = self.fe_concentration * (1 - x_intersect) * (self.total_mixing_volume / 2) / 1000
        complex_concentration = fe_initial_moles / (self.total_volume / 1000)
 
        if dissociation_degree > 0 and dissociation_degree < 1:
            apparent_constant = (1 - dissociation_degree) / (complex_concentration * dissociation_degree**2)
 
            lg_alpha = self.calculate_acid_effect()
            stability_constant = apparent_constant * (10**lg_alpha)
        else:
            stability_constant = float('inf')
            apparent_constant = 0
 
        results = {
            'D1': D1,
            'D2': D2,
            'x_intersect': x_intersect,
            'coordination_number': coordination_number,
            'dissociation_degree': dissociation_degree,
            'stability_constant': stability_constant,
            'apparent_constant': apparent_constant,
            'lg_alpha': lg_alpha,
            'left_fit': (m_left, c_left),
            'right_fit': (m_right, c_right),
            'complex_concentration': complex_concentration
        }
 
        return results
 
    def plot_results(self, results):
        if self.absorbances is None:
            raise ValueError
("请先输入数据!")  
        plt.figure(figsize=(12, 8))
 
        confidence_sizes = 100 + 200 * (1 / (self.absorbances_std + 0.001) / np.max(1 / (self.absorbances_std + 0.001)))
        plt.scatter(self.mole_fractions, self.absorbances, s=confidence_sizes, alpha=0.7, color='blue', label='实验数据点')
 
        for i, (x, y) in enumerate(zip(self.mole_fractions, self.absorbances)):
            plt.annotate(f'{y:.3f}', (x, y), textcoords="offset points", xytext=(0,10), ha='center', fontsize=9)
 
        if len(self.mole_fractions) > 3:
            spline = UnivariateSpline(self.mole_fractions, self.absorbances, s=0.001)
            x_smooth = np.linspace(0, 1, 200)
            y_smooth = spline(x_smooth)
            plt.plot(x_smooth, y_smooth, 'b-', alpha=0.7, linewidth=2, label='实验曲线')
 
        x_left = np.array([0.0, results['x_intersect']])
        x_right = np.array([results['x_intersect'], 1.0])
 
        m_left, c_left = results['left_fit']
        m_right, c_right = results['right_fit']
 
        x_fit_left = np.linspace(0.0, results['x_intersect'], 50)
        y_fit_left = m_left * x_fit_left + c_left
 
        x_fit_right = np.linspace(results['x_intersect'], 1.0, 50)
        y_fit_right = m_right * x_fit_right + c_right
 
        plt.plot(x_fit_left, y_fit_left, 'r--', linewidth=2, label='左侧拟合直线')
        plt.plot(x_fit_right, y_fit_right, 'g--', linewidth=2, label='右侧拟合直线')
 
        plt.text(0.1, 0.1, f'左侧: y = {m_left:.3f}x + {c_left:.3f}', transform=plt.gca().transAxes, fontsize=10, color='red')
        plt.text(0.1, 0.05, f'右侧: y = {m_right:.3f}x + {c_right:.3f}', transform=plt.gca().transAxes, fontsize=10, color='green')
 
        plt.axvline(x=results['x_intersect'], color='gray', linestyle=':', alpha=0.7, label=f'交点X坐标: {results["x_intersect"]:.3f}')
 
        plt.scatter([results['x_intersect']], [results['D1']], color='red', s=100, label='理论交点')
        plt.scatter([results['x_intersect']], [results['D2']], color='green', s=100, label='实际交点')
 
        plt.annotate(f'理论: {results["D1"]:.3f}', 
                    (results['x_intersect'], results['D1']), 
                    textcoords="offset points", xytext=(10,10), ha='left', fontsize=9, color='red')
 
        plt.annotate(f'实际: {results["D2"]:.3f}', 
                    (results['x_intersect'], results['D2']), 
                    textcoords="offset points", xytext=(10,-15), ha='left', fontsize=9, color='green')
 
        plt.xlabel('磺基水杨酸的摩尔分数')
        plt.ylabel('吸光度 (A)')
 
        plt.title('磺基水杨酸铁(Ⅲ)配合物组成与稳定常数测定结果图', fontsize=20, pad=20)
 
        plt.text(0.98, 0.98, "由何同学与D老师联合制作", 
                transform=plt.gca().transAxes,
                fontsize=6, color='gray', 
                ha='right', va='top', 
                bbox=dict(boxstyle="round,pad=0.2", facecolor="white", alpha=0.8))
 
        textstr = '\n'.join([
            f'配合物组成: ML{results["coordination_number"]:.1f}',
            f'交点X坐标: {results["x_intersect"]:.3f}',
            f'解离度: {results["dissociation_degree"]:.4f}',
            f'稳定常数: {results["stability_constant"]:.2e} L/mol'
        ])
        props = dict(boxstyle='round', facecolor='wheat', alpha=0.8)
        plt.text(0.65, 0.25, textstr, transform=plt.gca().transAxes, fontsize=12,
                verticalalignment='top', bbox=props)
 
        plt.grid(True, alpha=0.3)
        plt.legend(loc='upper left')
        plt.tight_layout()
        plt.show()
 
        return plt.gcf()
 
    def run_analysis(self):
        try:
            self.input_data()
            results = self.analyze_data()
 
            print("\n" + "="*60)
            print("实验结果分析")
            print("="*60)
            print(f"配合物组成: ML{results['coordination_number']:.1f}")
            print(f"交点X坐标: {results['x_intersect']:.3f}")
            print(f"解离度: {results['dissociation_degree']:.4f}")
            print(f"表观稳定常数: {results['apparent_constant']:.2e} L/mol")
            print(f"酸效应系数 lgα: {results['lg_alpha']:.3f}")
            print(f"热力学稳定常数: {results['stability_constant']:.2e} L/mol")
 
            print("\n生成图表中...")
            self.plot_results(results)
            print("图表已显示,请查看弹出窗口。")
 
        except Exception as e:
            print(f"错误: {e}")
            print("请检查输入数据格式是否正确。")
 
if __name__ == "__main__":
    experiment = SulfosalicylateIronExperiment()
    experiment.run_analysis()
				aW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKZnJvbSBzY2lweSBpbXBvcnQgc3RhdHMKZnJvbSBzY2lweS5pbnRlcnBvbGF0ZSBpbXBvcnQgVW5pdmFyaWF0ZVNwbGluZQppbXBvcnQgd2FybmluZ3MKd2FybmluZ3MuZmlsdGVyd2FybmluZ3MoJ2lnbm9yZScpCgpwbHQucmNQYXJhbXNbJ2ZvbnQuc2Fucy1zZXJpZiddID0gWydTaW1IZWknLCAnTWljcm9zb2Z0IFlhSGVpJywgJ0RlamFWdSBTYW5zJ10KcGx0LnJjUGFyYW1zWydheGVzLnVuaWNvZGVfbWludXMnXSA9IEZhbHNlCgpjbGFzcyBTdWxmb3NhbGljeWxhdGVJcm9uRXhwZXJpbWVudDoKICAgIGRlZiBfX2luaXRfXyhzZWxmKToKICAgICAgICBzZWxmLmZlX2NvbmNlbnRyYXRpb24gPSAwLjAwMQogICAgICAgIHNlbGYubGlnYW5kX2NvbmNlbnRyYXRpb24gPSAwLjAwMQogICAgICAgIHNlbGYuaGNsbzRfY29uY2VudHJhdGlvbiA9IDAuMDEKICAgICAgICBzZWxmLmhjbG80X3ZvbHVtZSA9IDEwLjAKICAgICAgICBzZWxmLnRvdGFsX21peGluZ192b2x1bWUgPSAxMC4wCiAgICAgICAgc2VsZi50b3RhbF92b2x1bWUgPSBzZWxmLmhjbG80X3ZvbHVtZSArIHNlbGYudG90YWxfbWl4aW5nX3ZvbHVtZQogICAgICAgIHNlbGYubW9sZV9mcmFjdGlvbnMgPSBucC5hcnJheShbMC4wLCAwLjEsIDAuMiwgMC4zLCAwLjQsIDAuNSwgMC42LCAwLjcsIDAuOCwgMC45LCAxLjBdKQogICAgICAgIHNlbGYuYWJzb3JiYW5jZXMgPSBOb25lCiAgICAgICAgc2VsZi5hYnNvcmJhbmNlc19hbGwgPSBOb25lCiAgICAgICAgCiAgICBkZWYgaW5wdXRfZGF0YShzZWxmKToKICAgICAgICBwcmludCgiPT09IOejuuWfuuawtOadqOmFuOmTgemFjeS9jeaVsOWSjOeos+WumuW4uOaVsOa1i+WumuWunumqjOaVsOaNruWkhOeQhiA9PT0iKQogICAgICAgIHByaW50KCJcbuivt+i+k+WFpTEx5Liq5pWw5o2u54K555qE5ZC45YWJ5bqm5YC8ICjlubPooYzlrp7pqowpOiIpCiAgICAgICAgcHJpbnQoIuagvOW8jzog54K5MeWunumqjDEg54K5MeWunumqjDIg54K5MuWunumqjDEg54K5MuWunumqjDIgLi4uIOeCuTEx5a6e6aqMMSDngrkxMeWunumqjDIiKQogICAgICAgIAogICAgICAgIGRhdGFfc3RyID0gaW5wdXQoIuivt+i+k+WFpTIy5Liq5ZC45YWJ5bqm5YC8OiAiKQogICAgICAgIGRhdGEgPSBsaXN0KG1hcChmbG9hdCwgZGF0YV9zdHIuc3BsaXQoKSkpCiAgICAgICAgCiAgICAgICAgaWYgbGVuKGRhdGEpICE9IDIyOgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCLlv4XpobvovpPlhaUyMuS4quWQuOWFieW6puWAvCEiKQogICAgICAgIAogICAgICAgIHNlbGYuYWJzb3JiYW5jZXNfYWxsID0gbnAuYXJyYXkoZGF0YSkucmVzaGFwZSgxMSwgMikKICAgICAgICBzZWxmLmFic29yYmFuY2VzID0gbnAubWVhbihzZWxmLmFic29yYmFuY2VzX2FsbCwgYXhpcz0xKQogICAgICAgIHNlbGYuYWJzb3JiYW5jZXNfc3RkID0gbnAuc3RkKHNlbGYuYWJzb3JiYW5jZXNfYWxsLCBheGlzPTEsIGRkb2Y9MSkKICAgICAgICAKICAgICAgICBwcmludCgiXG7mlbDmja7ovpPlhaXlrozmiJAhIikKICAgICAgICBwcmludChmIuaRqeWwlOWIhuaVsDoge3NlbGYubW9sZV9mcmFjdGlvbnN9IikKICAgICAgICBwcmludChmIuW5s+Wdh+WQuOWFieW6pjoge3NlbGYuYWJzb3JiYW5jZXN9IikKICAgICAgICAKICAgICAgICByZXR1cm4gc2VsZi5tb2xlX2ZyYWN0aW9ucywgc2VsZi5hYnNvcmJhbmNlcwogICAgCiAgICBkZWYgbGluZWFyX2ZpdChzZWxmLCB4X2RhdGEsIHlfZGF0YSk6CiAgICAgICAgQSA9IG5wLnZzdGFjayhbeF9kYXRhLCBucC5vbmVzKGxlbih4X2RhdGEpKV0pLlQKICAgICAgICBtLCBjID0gbnAubGluYWxnLmxzdHNxKEEsIHlfZGF0YSwgcmNvbmQ9Tm9uZSlbMF0KICAgICAgICByZXR1cm4gbSwgYwogICAgCiAgICBkZWYgY2FsY3VsYXRlX2ludGVyc2VjdGlvbihzZWxmLCBtMSwgYzEsIG0yLCBjMik6CiAgICAgICAgeF9pbnRlcnNlY3QgPSAoYzIgLSBjMSkgLyAobTEgLSBtMikKICAgICAgICB5X2ludGVyc2VjdCA9IG0xICogeF9pbnRlcnNlY3QgKyBjMQogICAgICAgIHJldHVybiB4X2ludGVyc2VjdCwgeV9pbnRlcnNlY3QKICAgIAogICAgZGVmIGZpbmRfY3VydmVfaW50ZXJzZWN0aW9uKHNlbGYsIHhfaW50ZXJzZWN0KToKICAgICAgICBmb3IgaSBpbiByYW5nZShsZW4oc2VsZi5tb2xlX2ZyYWN0aW9ucyktMSk6CiAgICAgICAgICAgIGlmIHNlbGYubW9sZV9mcmFjdGlvbnNbaV0gPD0geF9pbnRlcnNlY3QgPD0gc2VsZi5tb2xlX2ZyYWN0aW9uc1tpKzFdOgogICAgICAgICAgICAgICAgeDEsIHgyID0gc2VsZi5tb2xlX2ZyYWN0aW9uc1tpXSwgc2VsZi5tb2xlX2ZyYWN0aW9uc1tpKzFdCiAgICAgICAgICAgICAgICB5MSwgeTIgPSBzZWxmLmFic29yYmFuY2VzW2ldLCBzZWxmLmFic29yYmFuY2VzW2krMV0KICAgICAgICAgICAgICAgIHlfY3VydmUgPSB5MSArICh5MiAtIHkxKSAqICh4X2ludGVyc2VjdCAtIHgxKSAvICh4MiAtIHgxKQogICAgICAgICAgICAgICAgcmV0dXJuIHlfY3VydmUKICAgICAgICByZXR1cm4gc2VsZi5hYnNvcmJhbmNlc1tucC5hcmdtYXgoc2VsZi5hYnNvcmJhbmNlcyldCiAgICAKICAgIGRlZiBjYWxjdWxhdGVfYWNpZF9lZmZlY3Qoc2VsZiwgcEg9Mi41KToKICAgICAgICBIX3BsdXMgPSAxMCoqKC1wSCkKICAgICAgICBLMiA9IDEwKiooLTIuNikKICAgICAgICBLMyA9IDEwKiooLTExLjcpCiAgICAgICAgCiAgICAgICAgYWxwaGEgPSAxICsgSF9wbHVzL0szICsgSF9wbHVzKioyLyhLMipLMykKICAgICAgICBsZ19hbHBoYSA9IG5wLmxvZzEwKGFscGhhKQogICAgICAgIAogICAgICAgIHJldHVybiBsZ19hbHBoYQogICAgCiAgICBkZWYgYW5hbHl6ZV9kYXRhKHNlbGYpOgogICAgICAgIGlmIHNlbGYuYWJzb3JiYW5jZXMgaXMgTm9uZToKICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigi6K+35YWI6L6T5YWl5pWw5o2uISIpCiAgICAgICAgCiAgICAgICAgbGVmdF9pbmRpY2VzID0gWzAsIDEsIDJdCiAgICAgICAgcmlnaHRfaW5kaWNlcyA9IFs4LCA5LCAxMF0KICAgICAgICAKICAgICAgICBtX2xlZnQsIGNfbGVmdCA9IHNlbGYubGluZWFyX2ZpdCgKICAgICAgICAgICAgc2VsZi5tb2xlX2ZyYWN0aW9uc1tsZWZ0X2luZGljZXNdLCAKICAgICAgICAgICAgc2VsZi5hYnNvcmJhbmNlc1tsZWZ0X2luZGljZXNdCiAgICAgICAgKQogICAgICAgIAogICAgICAgIG1fcmlnaHQsIGNfcmlnaHQgPSBzZWxmLmxpbmVhcl9maXQoCiAgICAgICAgICAgIHNlbGYubW9sZV9mcmFjdGlvbnNbcmlnaHRfaW5kaWNlc10sIAogICAgICAgICAgICBzZWxmLmFic29yYmFuY2VzW3JpZ2h0X2luZGljZXNdCiAgICAgICAgKQogICAgICAgIAogICAgICAgIHhfaW50ZXJzZWN0LCBEMSA9IHNlbGYuY2FsY3VsYXRlX2ludGVyc2VjdGlvbihtX2xlZnQsIGNfbGVmdCwgbV9yaWdodCwgY19yaWdodCkKICAgICAgICAKICAgICAgICBEMiA9IHNlbGYuZmluZF9jdXJ2ZV9pbnRlcnNlY3Rpb24oeF9pbnRlcnNlY3QpCiAgICAgICAgCiAgICAgICAgY29vcmRpbmF0aW9uX251bWJlciA9IHhfaW50ZXJzZWN0IC8gKDEgLSB4X2ludGVyc2VjdCkKICAgICAgICAKICAgICAgICBpZiBEMSA+IDA6CiAgICAgICAgICAgIGRpc3NvY2lhdGlvbl9kZWdyZWUgPSAoRDEgLSBEMikgLyBEMQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGRpc3NvY2lhdGlvbl9kZWdyZWUgPSAwCiAgICAgICAgCiAgICAgICAgZmVfaW5pdGlhbF9tb2xlcyA9IHNlbGYuZmVfY29uY2VudHJhdGlvbiAqICgxIC0geF9pbnRlcnNlY3QpICogKHNlbGYudG90YWxfbWl4aW5nX3ZvbHVtZSAvIDIpIC8gMTAwMAogICAgICAgIGNvbXBsZXhfY29uY2VudHJhdGlvbiA9IGZlX2luaXRpYWxfbW9sZXMgLyAoc2VsZi50b3RhbF92b2x1bWUgLyAxMDAwKQogICAgICAgIAogICAgICAgIGlmIGRpc3NvY2lhdGlvbl9kZWdyZWUgPiAwIGFuZCBkaXNzb2NpYXRpb25fZGVncmVlIDwgMToKICAgICAgICAgICAgYXBwYXJlbnRfY29uc3RhbnQgPSAoMSAtIGRpc3NvY2lhdGlvbl9kZWdyZWUpIC8gKGNvbXBsZXhfY29uY2VudHJhdGlvbiAqIGRpc3NvY2lhdGlvbl9kZWdyZWUqKjIpCiAgICAgICAgICAgIAogICAgICAgICAgICBsZ19hbHBoYSA9IHNlbGYuY2FsY3VsYXRlX2FjaWRfZWZmZWN0KCkKICAgICAgICAgICAgc3RhYmlsaXR5X2NvbnN0YW50ID0gYXBwYXJlbnRfY29uc3RhbnQgKiAoMTAqKmxnX2FscGhhKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN0YWJpbGl0eV9jb25zdGFudCA9IGZsb2F0KCdpbmYnKQogICAgICAgICAgICBhcHBhcmVudF9jb25zdGFudCA9IDAKICAgICAgICAKICAgICAgICByZXN1bHRzID0gewogICAgICAgICAgICAnRDEnOiBEMSwKICAgICAgICAgICAgJ0QyJzogRDIsCiAgICAgICAgICAgICd4X2ludGVyc2VjdCc6IHhfaW50ZXJzZWN0LAogICAgICAgICAgICAnY29vcmRpbmF0aW9uX251bWJlcic6IGNvb3JkaW5hdGlvbl9udW1iZXIsCiAgICAgICAgICAgICdkaXNzb2NpYXRpb25fZGVncmVlJzogZGlzc29jaWF0aW9uX2RlZ3JlZSwKICAgICAgICAgICAgJ3N0YWJpbGl0eV9jb25zdGFudCc6IHN0YWJpbGl0eV9jb25zdGFudCwKICAgICAgICAgICAgJ2FwcGFyZW50X2NvbnN0YW50JzogYXBwYXJlbnRfY29uc3RhbnQsCiAgICAgICAgICAgICdsZ19hbHBoYSc6IGxnX2FscGhhLAogICAgICAgICAgICAnbGVmdF9maXQnOiAobV9sZWZ0LCBjX2xlZnQpLAogICAgICAgICAgICAncmlnaHRfZml0JzogKG1fcmlnaHQsIGNfcmlnaHQpLAogICAgICAgICAgICAnY29tcGxleF9jb25jZW50cmF0aW9uJzogY29tcGxleF9jb25jZW50cmF0aW9uCiAgICAgICAgfQogICAgICAgIAogICAgICAgIHJldHVybiByZXN1bHRzCiAgICAKICAgIGRlZiBwbG90X3Jlc3VsdHMoc2VsZiwgcmVzdWx0cyk6CiAgICAgICAgaWYgc2VsZi5hYnNvcmJhbmNlcyBpcyBOb25lOgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCLor7flhYjovpPlhaXmlbDmja4hIikKICAgICAgICAKICAgICAgICBwbHQuZmlndXJlKGZpZ3NpemU9KDEyLCA4KSkKICAgICAgICAKICAgICAgICBjb25maWRlbmNlX3NpemVzID0gMTAwICsgMjAwICogKDEgLyAoc2VsZi5hYnNvcmJhbmNlc19zdGQgKyAwLjAwMSkgLyBucC5tYXgoMSAvIChzZWxmLmFic29yYmFuY2VzX3N0ZCArIDAuMDAxKSkpCiAgICAgICAgcGx0LnNjYXR0ZXIoc2VsZi5tb2xlX2ZyYWN0aW9ucywgc2VsZi5hYnNvcmJhbmNlcywgcz1jb25maWRlbmNlX3NpemVzLCBhbHBoYT0wLjcsIGNvbG9yPSdibHVlJywgbGFiZWw9J+WunumqjOaVsOaNrueCuScpCiAgICAgICAgCiAgICAgICAgZm9yIGksICh4LCB5KSBpbiBlbnVtZXJhdGUoemlwKHNlbGYubW9sZV9mcmFjdGlvbnMsIHNlbGYuYWJzb3JiYW5jZXMpKToKICAgICAgICAgICAgcGx0LmFubm90YXRlKGYne3k6LjNmfScsICh4LCB5KSwgdGV4dGNvb3Jkcz0ib2Zmc2V0IHBvaW50cyIsIHh5dGV4dD0oMCwxMCksIGhhPSdjZW50ZXInLCBmb250c2l6ZT05KQogICAgICAgIAogICAgICAgIGlmIGxlbihzZWxmLm1vbGVfZnJhY3Rpb25zKSA+IDM6CiAgICAgICAgICAgIHNwbGluZSA9IFVuaXZhcmlhdGVTcGxpbmUoc2VsZi5tb2xlX2ZyYWN0aW9ucywgc2VsZi5hYnNvcmJhbmNlcywgcz0wLjAwMSkKICAgICAgICAgICAgeF9zbW9vdGggPSBucC5saW5zcGFjZSgwLCAxLCAyMDApCiAgICAgICAgICAgIHlfc21vb3RoID0gc3BsaW5lKHhfc21vb3RoKQogICAgICAgICAgICBwbHQucGxvdCh4X3Ntb290aCwgeV9zbW9vdGgsICdiLScsIGFscGhhPTAuNywgbGluZXdpZHRoPTIsIGxhYmVsPSflrp7pqozmm7Lnur8nKQogICAgICAgIAogICAgICAgIHhfbGVmdCA9IG5wLmFycmF5KFswLjAsIHJlc3VsdHNbJ3hfaW50ZXJzZWN0J11dKQogICAgICAgIHhfcmlnaHQgPSBucC5hcnJheShbcmVzdWx0c1sneF9pbnRlcnNlY3QnXSwgMS4wXSkKICAgICAgICAKICAgICAgICBtX2xlZnQsIGNfbGVmdCA9IHJlc3VsdHNbJ2xlZnRfZml0J10KICAgICAgICBtX3JpZ2h0LCBjX3JpZ2h0ID0gcmVzdWx0c1sncmlnaHRfZml0J10KICAgICAgICAKICAgICAgICB4X2ZpdF9sZWZ0ID0gbnAubGluc3BhY2UoMC4wLCByZXN1bHRzWyd4X2ludGVyc2VjdCddLCA1MCkKICAgICAgICB5X2ZpdF9sZWZ0ID0gbV9sZWZ0ICogeF9maXRfbGVmdCArIGNfbGVmdAogICAgICAgIAogICAgICAgIHhfZml0X3JpZ2h0ID0gbnAubGluc3BhY2UocmVzdWx0c1sneF9pbnRlcnNlY3QnXSwgMS4wLCA1MCkKICAgICAgICB5X2ZpdF9yaWdodCA9IG1fcmlnaHQgKiB4X2ZpdF9yaWdodCArIGNfcmlnaHQKICAgICAgICAKICAgICAgICBwbHQucGxvdCh4X2ZpdF9sZWZ0LCB5X2ZpdF9sZWZ0LCAnci0tJywgbGluZXdpZHRoPTIsIGxhYmVsPSflt6bkvqfmi5/lkIjnm7Tnur8nKQogICAgICAgIHBsdC5wbG90KHhfZml0X3JpZ2h0LCB5X2ZpdF9yaWdodCwgJ2ctLScsIGxpbmV3aWR0aD0yLCBsYWJlbD0n5Y+z5L6n5ouf5ZCI55u057q/JykKICAgICAgICAKICAgICAgICBwbHQudGV4dCgwLjEsIDAuMSwgZiflt6bkvqc6IHkgPSB7bV9sZWZ0Oi4zZn14ICsge2NfbGVmdDouM2Z9JywgdHJhbnNmb3JtPXBsdC5nY2EoKS50cmFuc0F4ZXMsIGZvbnRzaXplPTEwLCBjb2xvcj0ncmVkJykKICAgICAgICBwbHQudGV4dCgwLjEsIDAuMDUsIGYn5Y+z5L6nOiB5ID0ge21fcmlnaHQ6LjNmfXggKyB7Y19yaWdodDouM2Z9JywgdHJhbnNmb3JtPXBsdC5nY2EoKS50cmFuc0F4ZXMsIGZvbnRzaXplPTEwLCBjb2xvcj0nZ3JlZW4nKQogICAgICAgIAogICAgICAgIHBsdC5heHZsaW5lKHg9cmVzdWx0c1sneF9pbnRlcnNlY3QnXSwgY29sb3I9J2dyYXknLCBsaW5lc3R5bGU9JzonLCBhbHBoYT0wLjcsIGxhYmVsPWYn5Lqk54K5WOWdkOaghzoge3Jlc3VsdHNbInhfaW50ZXJzZWN0Il06LjNmfScpCiAgICAgICAgCiAgICAgICAgcGx0LnNjYXR0ZXIoW3Jlc3VsdHNbJ3hfaW50ZXJzZWN0J11dLCBbcmVzdWx0c1snRDEnXV0sIGNvbG9yPSdyZWQnLCBzPTEwMCwgbGFiZWw9J+eQhuiuuuS6pOeCuScpCiAgICAgICAgcGx0LnNjYXR0ZXIoW3Jlc3VsdHNbJ3hfaW50ZXJzZWN0J11dLCBbcmVzdWx0c1snRDInXV0sIGNvbG9yPSdncmVlbicsIHM9MTAwLCBsYWJlbD0n5a6e6ZmF5Lqk54K5JykKICAgICAgICAKICAgICAgICBwbHQuYW5ub3RhdGUoZifnkIborro6IHtyZXN1bHRzWyJEMSJdOi4zZn0nLCAKICAgICAgICAgICAgICAgICAgICAocmVzdWx0c1sneF9pbnRlcnNlY3QnXSwgcmVzdWx0c1snRDEnXSksIAogICAgICAgICAgICAgICAgICAgIHRleHRjb29yZHM9Im9mZnNldCBwb2ludHMiLCB4eXRleHQ9KDEwLDEwKSwgaGE9J2xlZnQnLCBmb250c2l6ZT05LCBjb2xvcj0ncmVkJykKICAgICAgICAKICAgICAgICBwbHQuYW5ub3RhdGUoZiflrp7pmYU6IHtyZXN1bHRzWyJEMiJdOi4zZn0nLCAKICAgICAgICAgICAgICAgICAgICAocmVzdWx0c1sneF9pbnRlcnNlY3QnXSwgcmVzdWx0c1snRDInXSksIAogICAgICAgICAgICAgICAgICAgIHRleHRjb29yZHM9Im9mZnNldCBwb2ludHMiLCB4eXRleHQ9KDEwLC0xNSksIGhhPSdsZWZ0JywgZm9udHNpemU9OSwgY29sb3I9J2dyZWVuJykKICAgICAgICAKICAgICAgICBwbHQueGxhYmVsKCfno7rln7rmsLTmnajphbjnmoTmkanlsJTliIbmlbAnKQogICAgICAgIHBsdC55bGFiZWwoJ+WQuOWFieW6piAoQSknKQogICAgICAgIAogICAgICAgIHBsdC50aXRsZSgn56O65Z+65rC05p2o6YW46ZOBKOKFoinphY3lkIjniannu4TmiJDkuI7nqLPlrprluLjmlbDmtYvlrprnu5Pmnpzlm74nLCBmb250c2l6ZT0yMCwgcGFkPTIwKQogICAgICAgIAogICAgICAgIHBsdC50ZXh0KDAuOTgsIDAuOTgsICLnlLHkvZXlkIzlrabkuI5E6ICB5biI6IGU5ZCI5Yi25L2cIiwgCiAgICAgICAgICAgICAgICB0cmFuc2Zvcm09cGx0LmdjYSgpLnRyYW5zQXhlcywKICAgICAgICAgICAgICAgIGZvbnRzaXplPTYsIGNvbG9yPSdncmF5JywgCiAgICAgICAgICAgICAgICBoYT0ncmlnaHQnLCB2YT0ndG9wJywgCiAgICAgICAgICAgICAgICBiYm94PWRpY3QoYm94c3R5bGU9InJvdW5kLHBhZD0wLjIiLCBmYWNlY29sb3I9IndoaXRlIiwgYWxwaGE9MC44KSkKICAgICAgICAKICAgICAgICB0ZXh0c3RyID0gJ1xuJy5qb2luKFsKICAgICAgICAgICAgZifphY3lkIjniannu4TmiJA6IE1Me3Jlc3VsdHNbImNvb3JkaW5hdGlvbl9udW1iZXIiXTouMWZ9JywKICAgICAgICAgICAgZifkuqTngrlY5Z2Q5qCHOiB7cmVzdWx0c1sieF9pbnRlcnNlY3QiXTouM2Z9JywKICAgICAgICAgICAgZifop6PnprvluqY6IHtyZXN1bHRzWyJkaXNzb2NpYXRpb25fZGVncmVlIl06LjRmfScsCiAgICAgICAgICAgIGYn56iz5a6a5bi45pWwOiB7cmVzdWx0c1sic3RhYmlsaXR5X2NvbnN0YW50Il06LjJlfSBML21vbCcKICAgICAgICBdKQogICAgICAgIHByb3BzID0gZGljdChib3hzdHlsZT0ncm91bmQnLCBmYWNlY29sb3I9J3doZWF0JywgYWxwaGE9MC44KQogICAgICAgIHBsdC50ZXh0KDAuNjUsIDAuMjUsIHRleHRzdHIsIHRyYW5zZm9ybT1wbHQuZ2NhKCkudHJhbnNBeGVzLCBmb250c2l6ZT0xMiwKICAgICAgICAgICAgICAgIHZlcnRpY2FsYWxpZ25tZW50PSd0b3AnLCBiYm94PXByb3BzKQogICAgICAgIAogICAgICAgIHBsdC5ncmlkKFRydWUsIGFscGhhPTAuMykKICAgICAgICBwbHQubGVnZW5kKGxvYz0ndXBwZXIgbGVmdCcpCiAgICAgICAgcGx0LnRpZ2h0X2xheW91dCgpCiAgICAgICAgcGx0LnNob3coKQogICAgICAgIAogICAgICAgIHJldHVybiBwbHQuZ2NmKCkKICAgIAogICAgZGVmIHJ1bl9hbmFseXNpcyhzZWxmKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuaW5wdXRfZGF0YSgpCiAgICAgICAgICAgIHJlc3VsdHMgPSBzZWxmLmFuYWx5emVfZGF0YSgpCiAgICAgICAgICAgIAogICAgICAgICAgICBwcmludCgiXG4iICsgIj0iKjYwKQogICAgICAgICAgICBwcmludCgi5a6e6aqM57uT5p6c5YiG5p6QIikKICAgICAgICAgICAgcHJpbnQoIj0iKjYwKQogICAgICAgICAgICBwcmludChmIumFjeWQiOeJqee7hOaIkDogTUx7cmVzdWx0c1snY29vcmRpbmF0aW9uX251bWJlciddOi4xZn0iKQogICAgICAgICAgICBwcmludChmIuS6pOeCuVjlnZDmoIc6IHtyZXN1bHRzWyd4X2ludGVyc2VjdCddOi4zZn0iKQogICAgICAgICAgICBwcmludChmIuino+emu+W6pjoge3Jlc3VsdHNbJ2Rpc3NvY2lhdGlvbl9kZWdyZWUnXTouNGZ9IikKICAgICAgICAgICAgcHJpbnQoZiLooajop4LnqLPlrprluLjmlbA6IHtyZXN1bHRzWydhcHBhcmVudF9jb25zdGFudCddOi4yZX0gTC9tb2wiKQogICAgICAgICAgICBwcmludChmIumFuOaViOW6lOezu+aVsCBsZ86xOiB7cmVzdWx0c1snbGdfYWxwaGEnXTouM2Z9IikKICAgICAgICAgICAgcHJpbnQoZiLng63lipvlrabnqLPlrprluLjmlbA6IHtyZXN1bHRzWydzdGFiaWxpdHlfY29uc3RhbnQnXTouMmV9IEwvbW9sIikKICAgICAgICAgICAgCiAgICAgICAgICAgIHByaW50KCJcbueUn+aIkOWbvuihqOS4rS4uLiIpCiAgICAgICAgICAgIHNlbGYucGxvdF9yZXN1bHRzKHJlc3VsdHMpCiAgICAgICAgICAgIHByaW50KCLlm77ooajlt7LmmL7npLrvvIzor7fmn6XnnIvlvLnlh7rnqpflj6PjgIIiKQogICAgICAgICAgICAKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgIHByaW50KGYi6ZSZ6K+vOiB7ZX0iKQogICAgICAgICAgICBwcmludCgi6K+35qOA5p+l6L6T5YWl5pWw5o2u5qC85byP5piv5ZCm5q2j56Gu44CCIikKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBleHBlcmltZW50ID0gU3VsZm9zYWxpY3lsYXRlSXJvbkV4cGVyaW1lbnQoKQogICAgZXhwZXJpbWVudC5ydW5fYW5hbHlzaXMoKQ==