
#数理最適化のためのデータ加工から出力までを実行した．

import os
import numpy as np
import pandas as pd
import math
import time
import ilp

inf = 10 ** 9 + 7

#calc aijcij

class Calc_aij_cij:
    def __init__(self, dataset, shifts):
        self.dataset = dataset
        self.shifts = shifts
 
    def generate_id_time_list(self):
        _data = {}
        in_uIDs = set(self.dataset["in_uID"])
        n = len(in_uIDs)
        time_list = []
        for i in range(25):
            for j in range(i + 2, 25):
                if j - i <= 6:
                    time_list.append([i,j%24])
        m = len(time_list)
        #sum of SEW
        E = self.dataset["distributedSEW"].sum()
        in_uIDs = list(in_uIDs)
        in_uIDs.sort()
        _data.update({"in_uIDs":in_uIDs})
        _data.update({"n":n})
        _data.update({"time_list":time_list})
        _data.update({"m":m})
        _data.update({"E":E})
        return(_data)

    def energy_reduction(self, sew, temp_diff):
        if temp_diff > 0:
            reduction = (1 - 0.91 ** temp_diff) * sew
        elif temp_diff < 0:
            reduction = - (1/(0.91 ** abs(temp_diff)) - 1) * sew
        else:
            reduction = 0
        return reduction

    def generate_schedule_list(self, set_temp, time_list):
        return [dict(period = d, set = set_temp) for d in time_list]

    def calc_reduction_for_sch(self, in_uID_data, sch):
        derived_sew_data = in_uID_data.query("hour >= {0} & hour < {1}".format(sch["period"][0], sch["period"][1]))
        
        sews = derived_sew_data["distributedSEW"]
        temp_diffs = (sch["set"] - derived_sew_data["RTMP"])
        
        cij = sum(temp_diffs)
        if cij >= 0:
            cij = cij
        else:
            cij = 0
        
        aij = sum(list(map(self.energy_reduction, sews, temp_diffs)))
        
        if aij == 0 and cij == 0:
            cij = inf

        return (aij, cij)

    def apply_schedule_for_unit(self, in_uID_data, schedule_list_type):
        aij_list_for_unit = []
        cij_list_for_unit = []
        
        for sch in schedule_list_type:
            _res_type = self.calc_reduction_for_sch(in_uID_data, sch)
            aij_list_for_unit.append(_res_type[0])
            cij_list_for_unit.append(_res_type[1])
            
        a_c_table_type = pd.DataFrame([])
        a_c_table_type["aij"] = aij_list_for_unit
        a_c_table_type["cij"] = cij_list_for_unit
        return a_c_table_type


    def generate_aij_cij(self, in_uIDs, n, time_list, m):
        aij_results = np.empty((n,0))
        cij_results = np.empty((n,0))
        aij_cij_results = {}
        results = {}

        for shift in range(self.shifts):
            aij_rslt_full = []
            cij_rslt_full = []
            for in_uID in in_uIDs:
                in_uID_data = self.dataset.query("in_uID == {}".format(in_uID))
                temp = math.ceil(in_uID_data["RTMP"].mean()) + shift
                if temp >= 28:
                    temp = 28

                schedule_list = self.generate_schedule_list(temp, time_list)
                result = self.apply_schedule_for_unit(in_uID_data,schedule_list)
                results.update({str(in_uID):result})
            #print(results)
            for j in range(n):
                aij_rslt_each = []
                cij_rslt_each = []
                for k in range(m):
                    aij_rslt_each.append(list(results.values())[j].values[k][0])
                    cij_rslt_each.append(list(results.values())[j].values[k][1])
                aij_rslt_full.append(aij_rslt_each)
                cij_rslt_full.append(cij_rslt_each)
            aij_rslt_np_full = np.array(aij_rslt_full)
            cij_rslt_np_full = np.array(cij_rslt_full)
            #print(aij_rslt_np_full)
            aij_results = np.concatenate((aij_results,aij_rslt_np_full), axis = 1)
            cij_results = np.concatenate((cij_results,cij_rslt_np_full), axis = 1)
        # print(aij_results)
        # print(len(aij_results))
        # print(len(aij_results[3]))
        aij_cij_results.update({"aij": aij_results})
        aij_cij_results.update({"cij": cij_results})
        return (aij_cij_results)

class Solve_ilp:
    def __init__(self, p, total):
        self.p = p
        self.total = total
    
    def solve_ilp(self, data_1, data_2, aij_cij_1, aij_cij_2, shifts_1, shifts_2, E_all):
        p_cost = {}
        p1 = (E_all / data_1["E"]) * self.p
        p2 = (E_all / data_2["E"]) * (self.total - self.p)
        print(p1, p2)
        value1 = ilp.Ilp(aij_cij_1["aij"], aij_cij_1["cij"], data_1["E"], p1, data_1["n"], data_1["m"], shifts_1)
        value2 = ilp.Ilp(aij_cij_2["aij"], aij_cij_2["cij"], data_2["E"], p2, data_2["n"], data_2["m"], shifts_2)
        cost = value1.solve_ilp() + value2.solve_ilp()
        p_cost.update({"cost": cost})
        p_cost.update({"p1": p1})
        p_cost.update({"p2": p2})
        return (p_cost)

class Apply_datasets:
    def __init__(self, dataset_folder):
        self.dataset_folder = dataset_folder
    def apply_datasets(self, dataset_1st, dataset_2nd, dataset_3rd):
        aij_cij_data = {}
        
        #1つ目のファイル
        dataset_f = pd.read_csv("{0}/{1}".format(self.dataset_folder, dataset_1st))
        # print("Input shifts of {}".format(dataset_1st))
        # shifts_f = int(input())
        shifts_f = 2
        rslt_aij_cij_f = Calc_aij_cij(dataset_f, shifts_f)
        data_f = rslt_aij_cij_f.generate_id_time_list()
        aij_cij_f = rslt_aij_cij_f.generate_aij_cij(data_f["in_uIDs"], data_f["n"], data_f["time_list"], data_f["m"])
        
        #2つ目のファイル
        dataset_s = pd.read_csv("{0}/{1}".format(self.dataset_folder, dataset_2nd))
        # print("Input shifts of {}".format(dataset_2nd))
        # shifts_s = int(input())
        shifts_s = 2
        rslt_aij_cij_s = Calc_aij_cij(dataset_s, shifts_s)
        data_s = rslt_aij_cij_s.generate_id_time_list()
        aij_cij_s = rslt_aij_cij_s.generate_aij_cij(data_s["in_uIDs"], data_s["n"], data_s["time_list"], data_s["m"])
        
        #3つめのファイル
        dataset_t = pd.read_csv("{0}/{1}".format(self.dataset_folder, dataset_3rd))
        # print("Input shifts of {}".format(dataset_3rd))
        # shifts_t = int(input())
        shifts_t = 2
        rslt_aij_cij_t = Calc_aij_cij(dataset_t, shifts_t)
        data_t = rslt_aij_cij_t.generate_id_time_list()
        aij_cij_t = rslt_aij_cij_t.generate_aij_cij(data_t["in_uIDs"], data_t["n"], data_t["time_list"], data_t["m"])
        
        #辞書の更新
        aij_cij_data.update({"dataset_f": dataset_f})
        aij_cij_data.update({"data_f": data_f})
        aij_cij_data.update({"aij_cij_f": aij_cij_f})
        aij_cij_data.update({"shifts_f": shifts_f})
        aij_cij_data.update({"dataset_s": dataset_s})
        aij_cij_data.update({"data_s": data_s})
        aij_cij_data.update({"aij_cij_s": aij_cij_s})
        aij_cij_data.update({"shifts_s": shifts_s})
        aij_cij_data.update({"dataset_t": dataset_t})
        aij_cij_data.update({"data_t": data_t})
        aij_cij_data.update({"aij_cij_t": aij_cij_t})
        aij_cij_data.update({"shifts_t": shifts_t})
        return (aij_cij_data)

class Ternary_search:
    def __init__(self, dataset_folder, total):
        self.dataset_folder = dataset_folder
        self.dataset_files = list(os.listdir(self.dataset_folder))
        # print(self.dataset_files)
        self.total = total

    def files(self):
        dataset_1st = self.dataset_files[2]
        dataset_2nd = self.dataset_files[1]
        dataset_3rd = self.dataset_files[3]
        return (dataset_1st, dataset_2nd, dataset_3rd)

    def ternary_search_sub(self, left_side, calc_data, E_all):
        p_cost = {}
        left = 0
        right = self.total - left_side
        total = right
        while True:
            #pの探索を開始する
            #p2を探索する
            cmp1 = (left * 2 + right) / 3
            cmp2 = (left + right * 2) / 3
            solve_cmp1 = Solve_ilp(cmp1, total) 
            cost_cmp1 = solve_cmp1.solve_ilp(calc_data["data_s"], calc_data["data_t"], calc_data["aij_cij_s"], calc_data["aij_cij_t"], calc_data["shifts_s"], calc_data["shifts_t"], E_all)
            solve_cmp2 = Solve_ilp(cmp2, total) 
            cost_cmp2 = solve_cmp2.solve_ilp(calc_data["data_s"], calc_data["data_t"], calc_data["aij_cij_s"], calc_data["aij_cij_t"], calc_data["shifts_s"], calc_data["shifts_t"], E_all)

            if cost_cmp1["cost"] <= cost_cmp2["cost"]:
                right = cmp2
            else:
                left = cmp1

            if cost_cmp1["cost"] == cost_cmp2["cost"]:
                print(cmp1, cmp2)
                cost = cost_cmp1["cost"]
                p1 = cost_cmp1["p1"]
                p2 = cost_cmp1["p2"]
                p_cost.update({"cost": cost})
                p_cost.update({"p1": p1})
                p_cost.update({"p2": p2})
                break
        return (p_cost)

    def ternary_search(self):
        p_cost = {}
        left = 0
        right = self.total
        datasets = self.files()
        print(datasets)
        dataset_1st = datasets[0]
        dataset_2nd = datasets[1]
        dataset_3rd = datasets[2]
        apply_data = Apply_datasets(self.dataset_folder)
        calc_data = apply_data.apply_datasets(dataset_1st, dataset_2nd, dataset_3rd)
        E_all = calc_data["data_f"]["E"] + calc_data["data_s"]["E"] + calc_data["data_t"]["E"]

        p0 = None
        p1 = None
        p2 = None
        # cost = None
        while True:
              #p1を固定する
              cmp1 = (left * 2 + right) / 3
              cmp2 = (left + right * 2) / 3

              cmp1_subcost = self.ternary_search_sub(cmp1, calc_data, E_all)
              #p0の計算をする
              p0_cmp1 = cmp1 * (E_all / calc_data["data_f"]["E"])
              #p0の時の計算をする
              p0_cmp1_cost = ilp.Ilp(calc_data["aij_cij_f"]["aij"], calc_data["aij_cij_f"]["cij"], calc_data["data_f"]["E"], p0_cmp1, calc_data["data_f"]["n"], calc_data["data_f"]["m"], calc_data["shifts_f"])
              p0_cmp1_allcost = cmp1_subcost["cost"] + p0_cmp1_cost.solve_ilp()

              cmp2_subcost = self.ternary_search_sub(cmp2, calc_data, E_all)
              #p0の計算をする
              p0_cmp2 = cmp2 * (E_all / calc_data["data_f"]["E"])
              #p0の時の計算
              p0_cmp2_cost = ilp.Ilp(calc_data["aij_cij_f"]["aij"], calc_data["aij_cij_f"]["cij"], calc_data["data_f"]["E"], p0_cmp2, calc_data["data_f"]["n"], calc_data["data_f"]["m"], calc_data["shifts_f"])
              p0_cmp2_allcost = cmp2_subcost["cost"] + p0_cmp2_cost.solve_ilp()
              
              if p0_cmp1_allcost <= p0_cmp2_allcost:
                right = cmp2
              else:
                left = cmp1

              if p0_cmp1_allcost == p0_cmp2_allcost:
                p0 = p0_cmp1
                p1 = cmp1_subcost["p1"]
                p2 = cmp1_subcost["p2"]
                p_cost.update({"p0": p0})
                p_cost.update({"p1": p1})
                p_cost.update({"p2": p2})
                break
        return (p_cost, calc_data) 

class Output_plan:
    def __init__(self, p_cost, calc_data):
        self.p_cost = p_cost
        self.calc_data = calc_data
    def output_plan(self):
        plan = []
        #1つめのファイル
        aij_f = self.calc_data["aij_cij_f"]["aij"]
        cij_f = self.calc_data["aij_cij_f"]["cij"]
        E_f = self.calc_data["data_f"]["E"]
        p0 = self.p_cost["p0"]
        n_f = self.calc_data["data_f"]["n"]
        m_f = self.calc_data["data_f"]["m"]
        shifts_f = self.calc_data["shifts_f"]
        solve_f = ilp.Ilp_plan(aij_f, cij_f, E_f, p0, n_f, m_f, shifts_f)
        plan_aij_cij_f = solve_f.solve_ilp_plan()
        plan_f = plan_aij_cij_f[0]
        aij_f = plan_aij_cij_f[1]
        cij_f = plan_aij_cij_f[2]
        for i in range(len(plan_f)):
            in_uID_f = self.calc_data["data_f"]["in_uIDs"][plan_f[i][0]]
            in_uID_data = self.calc_data["dataset_f"].query("in_uID == {}".format(in_uID_f))
            tmp_f = math.ceil(in_uID_data["RTMP"].mean()) + plan_f[i][1] // m_f
            if tmp_f >= 28:
                tmp_f = 28
            time_f = self.calc_data["data_f"]["time_list"][plan_f[i][1] % m_f]
            plan.append([in_uID_f, tmp_f, time_f[0], time_f[1]])
        
        #2つめのファイル
        aij_s = self.calc_data["aij_cij_s"]["aij"]
        cij_s = self.calc_data["aij_cij_s"]["cij"]
        E_s = self.calc_data["data_s"]["E"]
        p1 = self.p_cost["p1"]
        n_s = self.calc_data["data_s"]["n"]
        m_s = self.calc_data["data_s"]["m"]
        shifts_s = self.calc_data["shifts_s"]
        solve_s = ilp.Ilp_plan(aij_s, cij_s, E_s, p1, n_s, m_s, shifts_s)
        plan_aij_cij_s = solve_s.solve_ilp_plan()
        plan_s = plan_aij_cij_s[0]
        aij_s = plan_aij_cij_s[1]
        cij_s = plan_aij_cij_s[2]
        for i in range(len(plan_s)):
            in_uID_s = self.calc_data["data_s"]["in_uIDs"][plan_s[i][0]]
            in_uID_data = self.calc_data["dataset_s"].query("in_uID == {}".format(in_uID_s))
            tmp_s = math.ceil(in_uID_data["RTMP"].mean()) + plan_s[i][1] // m_s
            if tmp_s >= 28:
                tmp_s = 28
            time_s = self.calc_data["data_s"]["time_list"][plan_s[i][1] % m_s]
            plan.append([in_uID_s, tmp_s, time_s[0], time_s[1]])
        
        #3つめのファイル
        aij_t = self.calc_data["aij_cij_t"]["aij"]
        cij_t = self.calc_data["aij_cij_t"]["cij"]
        E_t = self.calc_data["data_t"]["E"]
        p2 = self.p_cost["p2"]
        n_t = self.calc_data["data_t"]["n"]
        m_t = self.calc_data["data_t"]["m"]
        shifts_t = self.calc_data["shifts_t"]
        solve_t = ilp.Ilp_plan(aij_t, cij_t, E_t, p2, n_t, m_t, shifts_t)
        plan_aij_cij_t = solve_t.solve_ilp_plan()
        plan_t = plan_aij_cij_t[0]
        aij_t = plan_aij_cij_t[1]
        cij_t = plan_aij_cij_t[2]
        for i in range(len(plan_t)):
            in_uID_t = self.calc_data["data_t"]["in_uIDs"][plan_t[i][0]]
            in_uID_data = self.calc_data["dataset_t"].query("in_uID == {}".format(in_uID_t))
            tmp_t = math.ceil(in_uID_data["RTMP"].mean()) + plan_t[i][1] // m_t
            if tmp_t >= 28:
                tmp_t = 28
            time_t = self.calc_data["data_t"]["time_list"][plan_t[i][1] % m_t]
            plan.append([in_uID_t, tmp_t, time_t[0], time_t[1]])
        plan_columns = ["in_uID", "tmp", "start", "end"]
        plan_DF = pd.DataFrame(plan, columns = plan_columns)
        print(aij_f, cij_f)
        print(aij_s, cij_s)
        print(aij_t, cij_t)
        return (plan_DF)

#フォルダパス
dataset_folder = "/Users/***/"
save_file = "/Users/hs/MyGraduation/"

#目標の削減割合
total = 5.0

start = time.time()

#三分探索アルゴリズムの適用
ts = Ternary_search(dataset_folder, total)
ts_cost_data = ts.ternary_search()

p = ts_cost_data[0]
calc_data = ts_cost_data[1]
output_plan = Output_plan(p, calc_data)

#最適スケジュールの出力
plan = output_plan.output_plan()
plan.to_csv(os.path.join(save_file, "ac_plan.csv"))

#実行時間の確認

end = time.time()

elapsed_time = end - start
print(elapsed_time)
