import os
import json
import torch
import numpy as np
from thirdparty.bop_toolkit.bop_toolkit_lib.inout import load_ply

def load_meshes(model_dir, obj_ids):
    meshes = {}
    for obj_id in obj_ids:
        fln = os.path.join(model_dir, f"obj_{obj_id:06d}.ply")
        print("Loading mesh file", fln, end='\r')
        meshes[obj_id] = load_ply(fln)
    print()
    return meshes

def load_mesh_db(model_dir):
    with open(os.path.join(model_dir, 'models_info.json'), 'r') as f:
        model_info = json.load(f)
    obj_ids = [int(obj_id) for obj_id in model_info.keys()]
    meshes = load_meshes(model_dir, obj_ids)
    mesh_db = {}
    for obj_id in obj_ids:
        info = model_info[str(obj_id)]
        is_sym_disc = len(info.get("symmetries_discrete",[])) > 0
        is_sym_cont = len(info.get("symmetries_continuous",[])) > 0
        cont_sym = []
        if is_sym_cont:
            cont_sym = info["symmetries_continuous"]
        points_np = np.array(meshes[obj_id]["pts"], dtype=np.float32)
        points = torch.tensor(points_np).to(torch.float32)
        if torch.cuda.is_available():
            points = points.cuda()
        mesh_db[obj_id] = {
                "is_symmetric": is_sym_disc or is_sym_cont,
                "continuous_sym": cont_sym,
                "diameter": info["diameter"],
                "points": points,
        }
        #print(f"Object {obj_id} is_symmetric: {is_sym_disc or is_sym_cont}")
        #for k, v in meshes[obj_id].items():
        #    mesh_db[k] = v
        #for k, v in model_info[str(obj_id)].items():
        #    mesh_db[k] = v
    return mesh_db

def load_meshes_DEBUG(model_dir, obj_ids, obj_map):
    meshes = {}
    for obj_id in obj_ids:
        fln = os.path.join(model_dir, obj_map[obj_id], "points.xyz")
        print("Loading mesh file", fln, end='\r')
        meshes[obj_id] = 1000 * np.loadtxt(fln, dtype=np.float32)
    print()
    return meshes

# For loading the original YCBV models for DEBUGGING ONLY
def load_mesh_db_DEBUG(model_dir, obj_map):
    obj_ids = [int(obj_id) for obj_id in obj_map.keys()]
    meshes = load_meshes_DEBUG(model_dir, obj_ids, obj_map)
    mesh_db = {}
    for obj_id in obj_ids:
        points_np = meshes[obj_id]
        points = torch.tensor(points_np).to(torch.float32)
        if torch.cuda.is_available():
            points = points.cuda()
        mesh_db[obj_id] = {
                "is_symmetric": False,
                "continuous_sym": False,
                "points": points,
                "points_np": points_np,
        }
        #print(f"Object {obj_id} is_symmetric: {is_sym_disc or is_sym_cont}")
        #for k, v in meshes[obj_id].items():
        #    mesh_db[k] = v
        #for k, v in model_info[str(obj_id)].items():
        #    mesh_db[k] = v
    return mesh_db


import tensorflow as tf
from tensorflow import keras

from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, LabelEncoder, OneHotEncoder, MinMaxScaler
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
import pickle

import pandas as pd

class PredictingManager:
    def __init__(self):
        self.model = keras.models.load_model("my_keras_model.h5")
        self.num_pipeline = Pipeline([
            ('imputer', SimpleImputer(strategy="mean")),
            ('std_scaler', StandardScaler()),
        ])
        self.num_attribs = ['Pclass', 'Age', 'SibSp', 'Parch', 'Fare']
        self.cat_attribs = ['Sex', 'Embarked']

        filename = '/app/full_pipeline.pkl'
        #filename = 'full_pipeline.pkl'
        with open(filename, 'rb') as f:
            self.full_pipeline = pickle.load(f)

    def do_predict(self,data):
        data = [[data['passengerClass'], data['age'], data['sibSp'], data['parCh'], data['fare'], data['sex'], data['embarked']]]
        df = pd.DataFrame(data, columns=['Pclass', 'Age', 'SibSp', 'Parch', 'Fare','Sex', 'Embarked'])
        print(df.head())
        X_data_prepared = self.full_pipeline.transform(df)
        print(X_data_prepared)
        predicted_class = self.model.predict_classes(X_data_prepared)
        predicted_prob = self.model.predict(X_data_prepared)
        predicted_class_str = str(predicted_class[0][0])
        predicted_prob_str = str(round(predicted_prob[0][0] * 100))
        print(predicted_class_str)
        print(predicted_prob_str)
        predict_data = [predicted_class_str, predicted_prob_str]
        return predict_data


import asyncio
import discord
import json
import random
import math
import time
import datetime
import os
import shutil
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
from discord.ext import commands
from discord.ext import tasks

class Stock(commands.Cog):

    def __init__(self, client):
        self.client = client
        print(f'{__name__} 로드 완료!')

    @commands.command(aliases=["차트", "매수", "매도", "그래프", "주식정보", "주식구매", "주식판매"])
    async def 주식(self, ctx):
        await ctx.send("현재 점검중인 기능입니다.")

    @commands.command()
    async def 계정생성(self, ctx):
        user_id = str(ctx.author.id)
        with open("stock/usr_data.json", 'r') as f:
            user_data = json.load(f)  # 유저 데이터 불러오는 코드
        try:
            a = user_data[user_id]
            await ctx.send('이미 등록된 계정입니다.')
            return
        except KeyError:
            pass
        user_data[user_id] = {}
        user_data[user_id]["money"] = 1000
        user_data[user_id]['alba'] = None
        with open("stock/usr_data.json", 'w') as f:
            json.dump(user_data, f, indent=4)
        await ctx.send('계정을 생성했습니다!')

    @commands.command()
    async def 지갑(self, ctx, member: discord.Member = None):
        if member is None:
            member = ctx.author
        user_id = str(member.id)
        with open("stock/data.json", 'r') as f:
            stock_data = json.load(f)  # 주식 데이터 불러오는 코드
        with open("stock/usr_data.json", 'r') as f:
            user_data = json.load(f)  # 유저 데이터 불러오는 코드
        try:
            embed = discord.Embed(title='지갑', description=str(member.mention), colour=discord.Color.red())
            embed.add_field(name='돈', value=f'{str(user_data[user_id]["money"])}원', inline=False)
            await ctx.send(embed=embed)
        except KeyError:
            await ctx.send('계정을 먼저 생성해주세요.')

    @commands.command(aliases=["알바"])
    async def 알바하자(self, ctx):
        author_id = str(ctx.author.id)
        currenttime = time.strftime('%Y%m%d')
        with open(f'stock/usr_data.json', 'r') as f:
            money = json.load(f)
        try:
            if money[author_id]['alba'] is None:
                pass
            elif int(currenttime) == int(money[author_id]['alba']):
                await ctx.send("알바는 하루에 1번만 가능합니다.")
                return
            money_choices = [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8590]
            got_money = random.choice(money_choices)
            await ctx.send(f'돈이다. ({got_money}원)')
            money[author_id]['money'] += got_money
            money[author_id]['alba'] = currenttime
            with open(f'stock/usr_data.json', 'w') as s:
                json.dump(money, s, indent=4)
        except KeyError:
            await ctx.send('계정을 먼저 생성해주세요.')
            return

    @commands.command()
    async def 가즈아(self, ctx, amount):
        amount = int(amount)
        author_id = str(ctx.author.id)
        if amount <= 0:
            await ctx.send('1원 이상으로 입력해주세요.')
            return
        with open(f'stock/usr_data.json', 'r') as f:
            money = json.load(f)
        try:
            if amount > money[author_id]['money']:
                await ctx.send('돈이 부족합니다.')
                return
            money[author_id]['money'] -= amount
            amolang = ['yes', 'no', 'no']
            result = random.choice(amolang)
            if result == 'yes':
                await ctx.send('도박에 성공했습니다. 3배의 돈을 벌었습니다.')
                money[author_id]['money'] += amount * 4
            else:
                await ctx.send('도박에 실패했습니다. 돈을 잃었습니다.')
            with open(f'stock/usr_data.json', 'w') as s:
                json.dump(money, s, indent=4)
        except KeyError:
            await ctx.send('계정을 먼저 생성해주세요.')
            return

    @commands.command()
    async def 한강가즈아(self, ctx):
        author_id = str(ctx.author.id)
        try:
            with open(f'stock/usr_data.json', 'r') as f:
                money = json.load(f)
            del money[author_id]
            with open(f'stock/usr_data.json', 'w') as f:
                json.dump(money, f, indent=4)
        except KeyError:
            await ctx.send('계정을 먼저 생성해주세요.')
            return
        await ctx.send('당신은 한강에서 사망하셨습니다. 계정이 삭제되었습니다.')

    @commands.command()
    async def 가위바위보(self, ctx):
        user_id = str(ctx.author.id)
        with open("stock/usr_data.json", 'r') as f:
            user_data = json.load(f)  # 유저 데이터 불러오는 코드
        await ctx.send("`가위, 바위, 보` 중에서 하나를 5초 안에 말해주세요!")
        try:
            a = user_data[user_id]
        except KeyError:
            await ctx.send('계정을 먼저 생성해주세요.')
            return

        rpc = ['가위', '바위', '보']

        def check(m):
            return m.content == "가위" or m.content == "바위" or m.content == "보"

        def game(A, B):
            if A not in rpc:
                return
            if A == rpc[0] and B == rpc[2] or A == rpc[1] and B == rpc[0] or A == rpc[2] and B == rpc[1]:
                return 1
            elif A == B:
                return 2
            elif A == rpc[0] and B == rpc[1] or A == rpc[1] and B == rpc[2] or A == rpc[2] and B == rpc[0]:
                return 3

        try:
            answer = await self.client.wait_for("message", timeout=5, check=check)
        except asyncio.TimeoutError:
            await ctx.send("시간이 초과됬어요...")
            return

        choice = random.choice(rpc)
        result = game(answer.content, choice)
        if result == 1:
            await ctx.send(f"{ctx.author.mention}님이 이겼어요... ({answer.content}, {choice})\n`+100원`")
            user_data[user_id]["money"] += 100
        elif result == 3:
            await ctx.send(f"제가 이겼어요! ({answer.content}, {choice})")
        elif result == 2:
            await ctx.send(f"비겼네요. ({answer.content}, {choice})\n`+50원`")
            user_data[user_id]["money"] += 50
        with open("stock/usr_data.json", 'w') as f:
            json.dump(user_data, f, indent=4)

def setup(client):
    client.add_cog(Stock(client))


import logging
from typing import Optional, Union

from df_engine.core.keywords import TRANSITIONS, RESPONSE
from df_engine.core import Context, Actor
import df_engine.conditions as cnd

logger = logging.getLogger(__name__)

# First of all, to create a dialog agent, we need to create a dialog script.
# Below, `script` is the dialog script.
# A dialog script is a flow dictionary that can contain multiple script .
# Script are needed in order to divide a dialog into sub-dialogs and process them separately.
# For example, the separation can be tied to the topic of the dialog.
# In our example, there is one flow called greeting_flow.

# Inside each flow, we can describe a sub-dialog.
# Here we can also use keyword `LOCAL`, which we have considered in other examples.

# Flow describes a sub-dialog using linked nodes, each node has the keywords `RESPONSE` and `TRANSITIONS`.

# `RESPONSE` - contains the response that the dialog agent will return when transitioning to this node.
# `TRANSITIONS` - describes transitions from the current node to other nodes.
# `TRANSITIONS` are described in pairs:
#      - the node to which the agent will perform the transition
#      - the condition under which to make the transition
script = {
    "greeting_flow": {
        "start_node": {  # This is an initial node, it doesn't need an `RESPONSE`
            RESPONSE: "",
            TRANSITIONS: {"node1": cnd.exact_match("Hi")},  # If "Hi" == request of user then we make the transition
        },
        "node1": {
            RESPONSE: "Hi, how are you?",  # When the agent goes to node1, we return "Hi, how are you?"
            TRANSITIONS: {"node2": cnd.exact_match("i'm fine, how are you?")},
        },
        "node2": {
            RESPONSE: "Good. What do you want to talk about?",
            TRANSITIONS: {"node3": cnd.exact_match("Let's talk about music.")},
        },
        "node3": {
            RESPONSE: "Sorry, I can not talk about music now.",
            TRANSITIONS: {"node4": cnd.exact_match("Ok, goodbye.")},
        },
        "node4": {RESPONSE: "bye", TRANSITIONS: {"node1": cnd.exact_match("Hi")}},
        "fallback_node": {  # We get to this node if an error occurred while the agent was running
            RESPONSE: "Ooops",
            TRANSITIONS: {"node1": cnd.exact_match("Hi")},
        },
    }
}

# An actor is an object that processes user input replicas and returns responses
# To create the actor, you need to pass the script of the dialogue `script`
# And pass the initial node `start_label`
# and the node to which the actor will go in case of an error `fallback_label`
# If `fallback_label` is not set, then its value becomes equal to `start_label` by default
actor = Actor(script, start_label=("greeting_flow", "start_node"), fallback_label=("greeting_flow", "fallback_node"))

# turn_handler - a function is made for the convenience of working with an actor
def turn_handler(
    in_request: str, ctx: Union[Context, str, dict], actor: Actor, true_out_response: Optional[str] = None
):
    # Context.cast - gets an object type of [Context, str, dict] returns an object type of Context
    ctx = Context.cast(ctx)
    # Add in current context a next request of user
    ctx.add_request(in_request)
    # pass the context into actor and it returns updated context with actor response
    ctx = actor(ctx)
    # get last actor response from the context
    out_response = ctx.last_response
    # the next condition branching needs for testing
    if true_out_response is not None and true_out_response != out_response:
        msg = f"in_request={in_request} -> true_out_response != out_response: {true_out_response} != {out_response}"
        raise Exception(msg)
    else:
        logging.info(f"in_request={in_request} -> {out_response}")
    return out_response, ctx

# testing
testing_dialog = [
    ("Hi", "Hi, how are you?"),  # start_node -> node1
    ("i'm fine, how are you?", "Good. What do you want to talk about?"),  # node1 -> node2
    ("Let's talk about music.", "Sorry, I can not talk about music now."),  # node2 -> node3
    ("Ok, goodbye.", "bye"),  # node3 -> node4
    ("Hi", "Hi, how are you?"),  # node4 -> node1
    ("stop", "Ooops"),  # node1 -> fallback_node
    ("stop", "Ooops"),  # fallback_node -> fallback_node
    ("Hi", "Hi, how are you?"),  # fallback_node -> node1
    ("i'm fine, how are you?", "Good. What do you want to talk about?"),  # node1 -> node2
    ("Let's talk about music.", "Sorry, I can not talk about music now."),  # node2 -> node3
    ("Ok, goodbye.", "bye"),  # node3 -> node4
]

def run_test():
    ctx = {}
    for in_request, true_out_response in testing_dialog:
        _, ctx = turn_handler(in_request, ctx, actor, true_out_response=true_out_response)

# interactive mode
def run_interactive_mode(actor):
    ctx = {}
    while True:
        in_request = input("type your answer: ")
        _, ctx = turn_handler(in_request, ctx, actor)

if __name__ == "__main__":
    logging.basicConfig(
        format="%(asctime)s-%(name)15s:%(lineno)3s:%(funcName)20s():%(levelname)s - %(message)s",
        level=logging.INFO,
    )
    # run_test()
    run_interactive_mode(actor)


import streamlit as st
import pandas as pd
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Flatten
from keras.layers import Dropout
import keras as keras
# import plotly.express as px
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import acf,pacf
from statsmodels.tsa.stattools import adfuller
from statsmodels.tsa.arima_model import ARIMA
import statsmodels.api as sm
from keras.preprocessing.sequence import TimeseriesGenerator
from sklearn.preprocessing import MinMaxScaler
from keras.layers import Bidirectional
import warnings                                  # `do not disturbe` mode
warnings.filterwarnings ('ignore')

st.title('Time Series Forecasting Using LSTM')
# @st.cache
rad=st.sidebar.radio('Time series forecast',['Data ahead Forecasting','Observed vs Predicted','Mulite step forecast','Grid search','MAPE based choosing best Model'])

st.write("IMPORT DATA")
data_load_state = st.text('Loading data...')
data=st.file_uploader('Upload here',type=['CSV','xlsx','sav'])
if data is not None:
    data=pd.read_csv(data)
data_load_state.text("Done! (using st.cache)")
data['StartYear']=st.sidebar.selectbox('Start Year:', data['Year'])
data['EndYear']=st.sidebar.selectbox('End Year:', data['Year'])
mask = (data['Year'] > data['StartYear']) & (data['Year'] <= data['EndYear'])
data=data.loc[mask]
data['Year'] = pd.to_datetime(data['Year'])
Make_choice=st.sidebar.selectbox('Select your columns:', data.columns)
df=data[Make_choice].tolist()

if rad=='Data ahead Forecasting':
    scaler=MinMaxScaler(feature_range=(0,1))
    df1=scaler.fit_transform(np.array(df).reshape(-1,1))
    train=df1
    X_train, y_train = np.array(train), np.array(train)
    X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1))
    periods_input = st.number_input('Number of input:',min_value = 1, max_value = 365)
    n_input = periods_input
    n_features = 1
    regressor = Sequential()
    regressor.add(Bidirectional(LSTM(units = 100, return_sequences = True,activation='tanh' ,recurrent_activation='sigmoid',input_shape = (X_train.shape[1], 1))))
    regressor.add(Dropout(0.02))
    regressor.add(LSTM(units = 15, return_sequences = True))
    regressor.add(Dropout(0.02))
    regressor.add(LSTM(units = 25, return_sequences = True))
    regressor.add(Dropout(0.02))
    regressor.add(LSTM(units = 35))
    regressor.add(Dropout(0.02))
    regressor.add(Dense(units = 1,activation='linear'))
    optimizer = keras.optimizers.Adam(learning_rate=0.001)
    regressor.compile(optimizer = optimizer, loss = 'mean_squared_error')
    regressor.fit(X_train, y_train, epochs = 50, batch_size =len(train),shuffle=False)
    pd.set_option('display.float_format', '{:.2f}'.format)
    train_predicted=regressor.predict(X_train,verbose=0)
    train_predicted1=scaler.inverse_transform(train_predicted)
    df_Model_fitted=pd.DataFrame(train_predicted1)
    pred_list = []
    batch = train[-n_input:].reshape((1, n_input, n_features))
    for i in range(n_input):
        pred_list.append(regressor.predict(batch)[0])
        batch = np.append(batch[:,1:,:],[[pred_list[i]]],axis=1)
    pred_list1=scaler.inverse_transform(pred_list)
    df_forecast=pd.DataFrame(pred_list1)
    df_predicted=df_Model_fitted.append(df_forecast)
    path=st.text_input('Enter data freq:')
    df_predicted['Year']=pd.date_range(start=min(data['Year']), periods=len(df_predicted), freq=path)
    df_Observed=data[Make_choice].tolist()
    df_Observed=pd.DataFrame(df_Observed)
    n_input=periods_input
    NA_toObservedData=np.repeat(np.nan,n_input)
    NA_toObservedData=pd.DataFrame(NA_toObservedData)
    df_Observed=df_Observed.append(NA_toObservedData)
    df_Observed['Year']=pd.date_range(start=min(data['Year']), periods=len(df_predicted), freq=path)
    Final_df1=pd.merge(df_Observed,df_predicted,on='Year')
    Final_df1.columns=['Observed','Year','Predicted']
    LSTM_Model_FinalDF=Final_df1.reindex(columns=['Year','Observed','Predicted'])

    st.write(LSTM_Model_FinalDF)
    plt.plot(data['Year'],data[Make_choice])
    st.pyplot()
if rad=='Observed vs Predicted':
    import numpy as numpy
    import numpy as np
    def create_dataset(dataset, look_back=1):
        dataX, dataY = [], []
        for i in range(len(dataset)-look_back-1):
            a = dataset[i:(i+look_back), 0]
            dataX.append(a)
            dataY.append(dataset[i + look_back, 0])
        return numpy.array(dataX), numpy.array(dataY)

    numpy.random.seed(7)
    scaler = MinMaxScaler(feature_range=(0, 1))
    dataset = scaler.fit_transform(np.array(df).reshape(-1,1))
    periods_input = st.number_input('Traing time for the model:',min_value = 1, max_value = 100)
    periods_input=periods_input/100
    train_size = int(len(dataset)*periods_input)
    test_size = len(dataset) - train_size
    train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:]
    look_back = st.number_input('Look back:',min_value = 1, max_value = 12)
    trainX, trainY = create_dataset(train, look_back)
    # st.write(len(trainX),len(trainY))
    testX, testY = create_dataset(test, look_back)
    # # reshape input to be [samples, time steps, features]
    trainX = numpy.reshape(trainX, (trainX.shape[0], 1, trainX.shape[1]))
    #
    testX = numpy.reshape(testX, (testX.shape[0], 1, testX.shape[1]))
    model = Sequential()
    model = Sequential()
    model.add(LSTM(50, input_shape=(1, look_back)))
    model.add(Dense(1))
    model.compile(loss='mean_squared_error', optimizer='adam')
    model.fit(trainX, trainY, epochs=100, batch_size=1, verbose=2)
    trainPredict = model.predict(trainX)
    #
    testPredict = model.predict(testX)
# invert predictions
    trainPredict = scaler.inverse_transform(trainPredict)
    trainY = scaler.inverse_transform([trainY])
    testPredict = scaler.inverse_transform(testPredict)
    testY = scaler.inverse_transform([testY])

    #

    import numpy as np

    def mean_absolute_percentage_error(y_true, y_pred):
        y_true, y_pred = np.array(y_true), np.array(y_pred)
        return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

    # calculate root mean squared error
    y_true=trainY
    y_pred=trainPredict
    trainScore = mean_absolute_percentage_error(y_true, y_pred)
    print('Train Score: %.2f RMSE' % (trainScore))
    testScore = mean_absolute_percentage_error(testY, testPredict)
    print('Test Score: %.2f RMSE' % (testScore))
    # shift train predictions for plotting
    trainPredictPlot = numpy.empty_like(dataset)
    trainPredictPlot[:, :] = numpy.nan
    trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict
    # shift test predictions for plotting
    testPredictPlot = numpy.empty_like(dataset)
    testPredictPlot[:, :] = numpy.nan
    testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict
    # plot baseline and predictions
    plt.plot(scaler.inverse_transform(dataset))
    plt.plot(trainPredictPlot)
    plt.plot(testPredictPlot)
    st.pyplot()
if rad=='Mulite step forecast':
    ##
    from pandas import DataFrame
    from pandas import Series
    from pandas import concat
    from pandas import read_csv
    from pandas import datetime
    from sklearn.metrics import mean_squared_error
    from sklearn.preprocessing import MinMaxScaler
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import LSTM
    from math import sqrt
    from matplotlib import pyplot
    from numpy import array

    # date-time parsing function for loading the dataset
    def parser(x):
    	return datetime.strptime('190'+x, '%Y-%m')

    # convert time series into supervised learning problem
    def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
    	n_vars = 1 if type(data) is list else data.shape[1]
    	df = DataFrame(data)
    	cols, names = list(), list()
    	# input sequence (t-n, ... t-1)
    	for i in range(n_in, 0, -1):
    		cols.append(df.shift(i))
    		names += [('var%d(t-%d)' % (j+1, i)) for j in range(n_vars)]
    	# forecast sequence (t, t+1, ... t+n)
    	for i in range(0, n_out):
    		cols.append(df.shift(-i))
    		if i == 0:
    			names += [('var%d(t)' % (j+1)) for j in range(n_vars)]
    		else:
    			names += [('var%d(t+%d)' % (j+1, i)) for j in range(n_vars)]
    	# put it all together
    	agg = concat(cols, axis=1)
    	agg.columns = names
    	# drop rows with NaN values
    	if dropnan:
    		agg.dropna(inplace=True)
    	return agg

    # create a differenced series
    def difference(dataset, interval=1):
    	diff = list()
    	for i in range(interval, len(dataset)):
    		value = dataset[i] - dataset[i - interval]
    		diff.append(value)
    	return Series(diff)

    # transform series into train and test sets for supervised learning
    def prepare_data(series, n_test, n_lag, n_seq):
    	# extract raw values
    	raw_values = series
    	# transform data to be stationary
    	diff_series = difference(raw_values, 1)
    	diff_values = diff_series.values
    	diff_values = diff_values.reshape(len(diff_values), 1)
    	# rescale values to -1, 1
    	scaler = MinMaxScaler(feature_range=(-1, 1))
    	scaled_values = scaler.fit_transform(diff_values)
    	scaled_values = scaled_values.reshape(len(scaled_values), 1)
    	# transform into supervised learning problem X, y
    	supervised = series_to_supervised(scaled_values, n_lag, n_seq)
    	supervised_values = supervised.values
    	# split into train and test sets
    	train, test = supervised_values[0:-n_test], supervised_values[-n_test:]
    	return scaler, train, test

    # fit an LSTM network to training data
    def fit_lstm(train, n_lag, n_seq, n_batch, nb_epoch, n_neurons):
    	# reshape training into [samples, timesteps, features]
    	X, y = train[:, 0:n_lag], train[:, n_lag:]
    	X = X.reshape(X.shape[0], 1, X.shape[1])
    	# design network
    	model = Sequential()
    	model.add(LSTM(n_neurons, batch_input_shape=(n_batch, X.shape[1], X.shape[2]), stateful=True))
    	model.add(Dense(y.shape[1]))
    	model.compile(loss='mean_squared_error', optimizer='adam')
    	# fit network
    	for i in range(nb_epoch):
    		model.fit(X, y, epochs=1, batch_size=n_batch, verbose=0, shuffle=False)
    		model.reset_states()
    	return model

    # make one forecast with an LSTM,
    def forecast_lstm(model, X, n_batch):
    	# reshape input pattern to [samples, timesteps, features]
    	X = X.reshape(1, 1, len(X))
    	# make forecast
    	forecast = model.predict(X, batch_size=n_batch)
    	# convert to array
    	return [x for x in forecast[0, :]]

    # evaluate the persistence model
    def make_forecasts(model, n_batch, train, test, n_lag, n_seq):
    	forecasts = list()
    	for i in range(len(test)):
    		X, y = test[i, 0:n_lag], test[i, n_lag:]
    		# make forecast
    		forecast = forecast_lstm(model, X, n_batch)
    		# store the forecast
    		forecasts.append(forecast)
    	return forecasts

    # invert differenced forecast
    def inverse_difference(last_ob, forecast):
    	# invert first forecast
    	inverted = list()
    	inverted.append(forecast[0] + last_ob)
    	# propagate difference forecast using inverted first value
    	for i in range(1, len(forecast)):
    		inverted.append(forecast[i] + inverted[i-1])
    	return inverted

    # inverse data transform on forecasts
    def inverse_transform(series, forecasts, scaler, n_test):
    	inverted = list()
    	for i in range(len(forecasts)):
    		# create array from forecast
    		forecast = array(forecasts[i])
    		forecast = forecast.reshape(1, len(forecast))
    		# invert scaling
    		inv_scale = scaler.inverse_transform(forecast)
    		inv_scale = inv_scale[0, :]
    		# invert differencing
    		index = len(series) - n_test + i - 1
    		last_ob = series
    		inv_diff = inverse_difference(last_ob, inv_scale)
    		# store
    		inverted.append(inv_diff)
    	return inverted
    import numpy as np

    # MAPE
    def mean_absolute_percentage_error(y_true, y_pred):
        y_true, y_pred = np.array(y_true), np.array(y_pred)
        return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

    # evaluate the RMSE for each forecast time step
    def evaluate_forecasts(test, forecasts, n_lag, n_seq):
    	for i in range(n_seq):
    		actual = [row[i] for row in test]
    		predicted = [forecast[i] for forecast in forecasts]
    		MAPE = mean_absolute_percentage_error(actual, predicted)
    		print('t+%d MAPE: %f' % ((i+1), MAPE))

    # plot the forecasts in the context of the original dataset
    def plot_forecasts(series, forecasts, n_test):
    	# plot the entire dataset in blue
    	pyplot.plot(series)
    	# plot the forecasts in red
    	for i in range(len(forecasts)):
    		off_s = len(series) - n_test + i - 1
    		off_e = off_s + len(forecasts[i]) + 1
    		xaxis = [x for x in range(off_s, off_e)]
    		yaxis = [series[off_s]] + forecasts[i]
    		pyplot.plot(xaxis, yaxis, color='red')
    	# show the plot
    	st.pyplot()
    if __name__=='__main__':
        n_lag = st.number_input('Number of LAG:',min_value = 1, max_value = 365)
        n_seq = st.number_input('Number of sequence:',min_value = 1, max_value = 365)
        n_test = st.number_input('Number of Test cases:',min_value = 1, max_value = 365)
        n_epochs = st.number_input('Number of Epochs:',min_value = 1, max_value = 2500)
        n_batch = st.number_input('Number of batch:',min_value = 1, max_value = 1)
        n_neurons = st.number_input('Number of Neurons:',min_value = 1, max_value = 10)
        # prepare data
        series=df
        scaler, train, test = prepare_data(series, n_test, n_lag, n_seq)
        # fit model
        model = fit_lstm(train, n_lag, n_seq, n_batch, n_epochs, n_neurons)
        # make forecasts
        forecasts = make_forecasts(model, n_batch, train, test, n_lag, n_seq)
        # inverse transform forecasts and test
        forecasts = inverse_transform(series, forecasts, scaler, n_test+2)
        actual = [row[n_lag:] for row in test]
        actual = inverse_transform(series, actual, scaler, n_test+2)
        # evaluate forecasts
        evaluate_forecasts(actual, forecasts, n_lag, n_seq)
        # plot forecasts
        plot_forecasts(series, forecasts, n_test+2)
if rad=='Grid search':
    from math import sqrt
    from numpy import array
    from numpy import mean
    from pandas import DataFrame
    from pandas import concat
    from pandas import read_csv
    from sklearn.metrics import mean_squared_error
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import LSTM
    import numpy as np
    # split a univariate dataset into train/test sets
    def train_test_split(data, n_test):
    	return data[:-n_test], data[-n_test:]

    # transform list into supervised learning format
    def series_to_supervised(data, n_in=1, n_out=1):
    	df = DataFrame(data)
    	cols = list()
    	# input sequence (t-n, ... t-1)
    	for i in range(n_in, 0, -1):
    		cols.append(df.shift(i))
    	# forecast sequence (t, t+1, ... t+n)
    	for i in range(0, n_out):
    		cols.append(df.shift(-i))
    	# put it all together
    	agg = concat(cols, axis=1)
    	# drop rows with NaN values
    	agg.dropna(inplace=True)
    	return agg.values

    # root mean squared error or rmse
    # def measure_rmse(actual, predicted):
    # 	return sqrt(mean_squared_error(actual, predicted))

    # difference dataset
    def difference(data, order):
    	return [data[i] - data[i - order] for i in range(order, len(data))]

    # fit a model
    def model_fit(train, config):
    	# unpack config
    	n_input, n_nodes, n_epochs, n_batch, n_diff = config
    	# prepare data
    	if n_diff > 0:
    		train = difference(train, n_diff)
    	# transform series into supervised format
    	data = series_to_supervised(train, n_in=n_input)
    	# separate inputs and outputs
    	train_x, train_y = data[:, :-1], data[:, -1]
    	# reshape input data into [samples, timesteps, features]
    	n_features = 1
    	train_x = train_x.reshape((train_x.shape[0], train_x.shape[1], n_features))
    	# define model
    	model = Sequential()
    	model.add(LSTM(n_nodes, activation='relu', input_shape=(n_input, n_features)))
    	model.add(Dense(n_nodes, activation='relu'))
    	model.add(Dense(1))
    	model.compile(loss='mse', optimizer='adam')
    	# fit model
    	model.fit(train_x, train_y, epochs=n_epochs, batch_size=n_batch, verbose=0)
    	return model

    # forecast with the fit model
    def model_predict(model, history, config):
    	# unpack config
    	n_input, _, _, _, n_diff = config
    	# prepare data
    	correction = 0.0
    	if n_diff > 0:
    		correction = history[-n_diff]
    		history = difference(history, n_diff)
    	# reshape sample into [samples, timesteps, features]
    	x_input = array(history[-n_input:]).reshape((1, n_input, 1))
    	# forecast
    	yhat = model.predict(x_input, verbose=0)
    	return correction + yhat[0]

    def MAPE(test,predictions):
        mape = np.mean(np.abs((test - predictions)/test))*100
        return mape
    # walk-forward validation for univariate data
    def walk_forward_validation(data, n_test, cfg):
    	predictions = list()
    	# split dataset
    	train, test = train_test_split(data, n_test)
    	# fit model
    	model = model_fit(train, cfg)
    	# seed history with training dataset
    	history = [x for x in train]
    	# step over each time-step in the test set
    	for i in range(len(test)):
    		# fit model and make forecast for history
    		yhat = model_predict(model, history, cfg)
    		# store forecast in list of predictions
    		predictions.append(yhat)
    		# add actual observation to history for the next loop
    		history.append(test[i])
    	# estimate prediction error
    	error = MAPE(test, predictions)
    	print(' > %.3f' % error)
    	return error

    # score a model, return None on failure
    def repeat_evaluate(data, config, n_test, n_repeats=10):
    	# convert config to a key
    	key = str(config)
    	# fit and evaluate the model n times
    	scores = [walk_forward_validation(data, n_test, config) for _ in range(n_repeats)]
    	# summarize score
    	result = mean(scores)
    	print('> Model[%s] %.3f' % (key, result))
    	return (key, result)

    # grid search configs
    def grid_search(data, cfg_list, n_test):
    	# evaluate configs
    	scores = [repeat_evaluate(data, cfg, n_test) for cfg in cfg_list]
    	# sort configs by error, asc
    	scores.sort(key=lambda tup: tup[1])
    	return scores

    # create a list of configs to try
    def model_configs():
    	# define scope of configs
    	n_input = [12]
    	n_nodes = [500]
    	n_epochs = [150]
    	n_batch = [1, 150]
    	n_diff = [12]
    	# create configs
    	configs = list()
    	for i in n_input:
    		for j in n_nodes:
    			for k in n_epochs:
    				for l in n_batch:
    					for m in n_diff:
    						cfg = [i, j, k, l, m]
    						configs.append(cfg)
    	print('Total configs: %d' % len(configs))
    	return configs
    # if__name__=='__main__':

    # define dataset
    # series = df
    data = df
    # data split
    n_test = st.number_input('Enter the Number of test',min_value=1,max_value=365)
    # model configs
    cfg_list = model_configs()
    # grid search
    scores = grid_search(data, cfg_list, n_test)
    st.write(scores)
    # list top 10 configs
    # for cfg, error in scores[:3]:
    # 	st.write(cfg, error)
if rad=='MAPE based choosing best Model':
     st.write('WIP')


"""Producer base-class providing common utilites and functionality"""
import logging
import time
import socket

#from confluent_kafka import avro
from confluent_kafka.admin import AdminClient, NewTopic
from confluent_kafka.avro import AvroProducer

logger = logging.getLogger(__name__)

class Producer:
    """Defines and provides common functionality amongst Producers"""

    # Tracks existing topics across all Producer instances
    existing_topics = set(list())
    client_id = socket.gethostname()

    def __init__(
        self,
        topic_name,
        key_schema,
        value_schema=None,
        num_partitions=1, #single node broker?
        num_replicas=1,
    ):
        """Initializes a Producer object with basic settings"""
        self.topic_name = topic_name
        self.key_schema = key_schema
        self.value_schema = value_schema
        self.num_partitions = num_partitions
        self.num_replicas = num_replicas

        self.broker_properties = {
            "bootstrap.servers": "PLAINTEXT://localhost:9092",
            "schema.registry.url": "http://localhost:8081",
            "client.id": self.client_id
        }

        # If the topic does not already exist, try to create it
        #print(len(self.existing_topics))
        if self._topic_exists(self.topic_name) == False:
            #print(self.topic_name)
            ret = self.create_topic()
            if ret == 1:
                #print(len(self.existing_topics))
                Producer.existing_topics.add(self.topic_name)

        self.producer = AvroProducer(self.broker_properties,
                                     default_key_schema=self.key_schema,
                                     default_value_schema=self.value_schema)

    def _topic_exists(self, topic_name: str) -> bool:
         if topic_name in Producer.existing_topics:
            return True
         return False

    def create_topic(self) -> int:
        """Creates the producer topic if it does not already exist"""
        try:
            client = AdminClient({"bootstrap.servers": self.broker_properties["bootstrap.servers"]})
            topic_metadata = client.list_topics(timeout=200)
            topic_check = self.topic_name in set(t.topic for t in iter(topic_metadata.topics.values()))
            if  topic_check == True:
                 return -1

            futures = client.create_topics(
                [
                    NewTopic(
                        topic=self.topic_name,
                        num_partitions=self.num_partitions,
                        replication_factor=self.num_replicas
                        #config={
                           # "cleanup.policy": "delete",
                          #  "compression.type": "lz4",
                          #  "delete.retention.ms": "2000",
                         #   "file.delete.delay.ms": "2000",
                        #}
                    )
                ]
            )
            status = 1
            for topic, future in futures.items():
                try:
                    future.result()
                    logger.debug("topic created")
                except Exception as e:
                    logger.fatal(f"failed to create topic {self.topic_name}: {e}")
                    status = -1
        except Exception as ex:
            logger.warn(f"Error in create topic {self.topic_name}: {ex}")
            status = -1
        return status

    def time_millis(self) -> int:
        return int(round(time.time() * 1000))

    def close(self):
        """Prepares the producer for exit by cleaning up the producer"""
        if self.producer is not None:
            self.producer.flush()


import json
from knack.log import get_logger
from azure.cli.core.azclierror import ForbiddenError, RequiredArgumentMissingError, InvalidArgumentValueError
from azext_iot.common.utility import process_json_arg, handle_service_exception, scantree
from azext_iot.digitaltwins.common import ADTModelCreateFailurePolicy
from azext_iot.digitaltwins.providers.base import DigitalTwinsProvider
from azext_iot.sdk.digitaltwins.dataplane.models import ErrorResponseException
from tqdm import tqdm

logger = get_logger(__name__)
MAX_MODELS_API_LIMIT = 250

def get_model_dependencies(model, model_id_to_model_map=None):
    """Return a list of dependency DTMIs for a given model"""
    dependencies = []

    # Add everything that would have dependency DTMIs, worry about flattening later
    if "contents" in model:
        components = [item["schema"] for item in model["contents"] if item["@type"] == "Component"]
        dependencies.extend(components)

    if "extends" in model:
        dependencies.append(model['extends'])

    # Go through gathered items, get the DTMI references, and flatten if needed
    no_dup = set()
    for item in dependencies:
        # Models defined in a DTDL can implement extensions of up to two interfaces.
        # These interfaces can be in the form of a DTMI reference, or a nested model.
        if isinstance(item, str):
            # If its just a string, thats a single DTMI reference, so just add that to our set
            no_dup.add(item)
            # Calculate recursive dependencies if model id to model map is passed
            if model_id_to_model_map is not None:
                dep_model = model_id_to_model_map[item]
                no_dup.update(set(get_model_dependencies(dep_model, model_id_to_model_map)))
        elif isinstance(item, dict):
            # If its a single nested model, get its dtmi reference, dependencies and add them
            no_dup.update(set(get_model_dependencies(item, model_id_to_model_map)))
        elif isinstance(item, list):
            # If its a list, could have DTMIs or nested models
            for sub_item in item:
                if isinstance(sub_item, str):
                    # If there are strings in the list, that's a DTMI reference, so add it
                    no_dup.add(sub_item)
                    # Calculate recursive dependencies if model id to model map is passed
                    if model_id_to_model_map is not None:
                        sub_dep_model = model_id_to_model_map[sub_item]
                        no_dup.update(set(get_model_dependencies(sub_dep_model, model_id_to_model_map)))
                elif isinstance(sub_item, dict):
                    # This is a nested model. Now go get its dependencies and add them
                    no_dup.update(set(get_model_dependencies(sub_item, model_id_to_model_map)))

    return list(no_dup)

class ModelProvider(DigitalTwinsProvider):
    def __init__(self, cmd, name, rg=None):
        super(ModelProvider, self).__init__(
            cmd=cmd, name=name, rg=rg,
        )
        self.model_sdk = self.get_sdk().digital_twin_models

    def add(self,
            max_models_per_batch: int,
            models=None,
            from_directory=None,
            failure_policy=ADTModelCreateFailurePolicy.ROLLBACK.value):
        if not any([models, from_directory]):
            raise RequiredArgumentMissingError("Provide either --models or --from-directory.")

        # If both arguments are provided. --models wins.
        payload = []
        models_per_batch = max_models_per_batch
        if models:
            models_result = process_json_arg(content=models, argument_name="models")

            if isinstance(models_result, list):
                payload.extend(models_result)
            elif isinstance(models_result, dict):
                payload.append(models_result)

        elif from_directory:
            payload = self._process_directory(from_directory=from_directory)

        logger.info("Models payload %s", json.dumps(payload))

        models_created = []
        try:
            # Process models in batches if models to process exceed the API limit
            if len(payload) > MAX_MODELS_API_LIMIT:

                model_id_to_model_map = {}
                for model_def in payload:
                    model_id_to_model_map[model_def['@id']] = model_def

                # Create a dictionary to categorize models by their number of dependencies
                dep_count_to_models_map = {}
                for model in payload:
                    num_dependencies = len(get_model_dependencies(model, model_id_to_model_map))
                    if num_dependencies not in dep_count_to_models_map:
                        dep_count_to_models_map[num_dependencies] = []
                    dep_count_to_models_map[num_dependencies].append(model)

                # Sort by dependency count
                dep_count_to_models_tuples = sorted(dep_count_to_models_map.items())
                models_batch = []
                response = []
                pbar = tqdm(total=len(payload), desc='Creating models...', ascii=' #')
                # The tuples being iterated are sorted by dependency count, hence models with 0 dependencies go first,
                # followed by models with 1 dependency, then 2 dependencies and so on... This ensures that all dependencies
                # of each model being added were either already added in a previous iteration or are in the current payload.
                for _, models_list in dep_count_to_models_tuples:
                    while len(models_batch) + len(models_list) > models_per_batch:
                        num_models_to_add = models_per_batch - len(models_batch)
                        models_batch.extend(models_list[0:num_models_to_add])
                        response.extend(self.model_sdk.add(models_batch, raw=True).response.json())
                        models_created.extend([model['@id'] for model in models_batch])
                        pbar.update(len(models_batch))
                        # Remove the model ids which have been processed
                        models_list = models_list[num_models_to_add:]
                        models_batch = []
                    models_batch.extend(models_list)
                # Process the last set of model ids
                if len(models_batch) > 0:
                    pbar.update(len(models_batch))
                    response.extend(self.model_sdk.add(models_batch, raw=True).response.json())
                pbar.close()
                return response
            return self.model_sdk.add(payload, raw=True).response.json()
        except ErrorResponseException as e:
            if len(models_created) > 0:
                pbar.close()
                # Delete all models created by this operation when the failure policy is set to 'Rollback'
                if failure_policy == ADTModelCreateFailurePolicy.ROLLBACK.value:
                    logger.error(
                        "Error creating models. Deleting {} models created by this operation...".format(len(models_created))
                    )
                    # Models will be deleted in the reverse order they were created.
                    # Hence, ensuring each model's dependencies are deleted after deleting the model.
                    models_created.reverse()
                    for model_id in models_created:
                        self.delete(model_id)
                # Models created by this operation are not deleted when the failure policy is set to 'None'
                elif failure_policy == ADTModelCreateFailurePolicy.NONE.value:
                    logger.error(
                        "Error creating current model batch. Successfully created {} models.".format(len(models_created))
                    )
                else:
                    raise InvalidArgumentValueError(
                        "Invalid failure policy: {}. Supported values are: '{}' and '{}'".format(
                            failure_policy, ADTModelCreateFailurePolicy.ROLLBACK.value, ADTModelCreateFailurePolicy.NONE.value
                        )
                    )
            # @vilit - hack to customize 403's to have more specific error messages
            if e.response.status_code == 403:
                error_text = "Current principal access is forbidden. Please validate rbac role assignments."
                raise ForbiddenError(error_text)
            handle_service_exception(e)

    def _process_directory(self, from_directory):
        logger.debug(
            "Documents contained in directory: {}, processing...".format(from_directory)
        )
        payload = []
        for entry in scantree(from_directory):
            if all(
                [not entry.name.endswith(".json"), not entry.name.endswith(".dtdl")]
            ):
                logger.debug(
                    "Skipping {} - model file must end with .json or .dtdl".format(
                        entry.path
                    )
                )
                continue
            entry_json = process_json_arg(content=entry.path, argument_name=entry.name)
            payload.append(entry_json)

        return payload

    def get(self, id, get_definition=False):
        try:
            return self.model_sdk.get_by_id(
                id=id, include_model_definition=get_definition, raw=True
            ).response.json()
        except ErrorResponseException as e:
            handle_service_exception(e)

    def list(
        self, get_definition=False, dependencies_for=None, top=None
    ):  # top is guarded for int() in arg def
        from azext_iot.sdk.digitaltwins.dataplane.models import DigitalTwinModelsListOptions

        list_options = DigitalTwinModelsListOptions(max_items_per_page=top)

        return self.model_sdk.list(
            dependencies_for=dependencies_for,
            include_model_definition=get_definition,
            digital_twin_models_list_options=list_options,
        )

    def update(self, id, decommission: bool):
        patched_model = [
            {"op": "replace", "path": "/decommissioned", "value": decommission}
        ]

        # Does not return model object upon updating
        try:
            self.model_sdk.update(id=id, update_model=patched_model)
        except ErrorResponseException as e:
            handle_service_exception(e)

        return self.get(id=id)

    def delete(self, id: str):
        try:
            self.model_sdk.delete(id=id)
        except ErrorResponseException as e:
            handle_service_exception(e)

    def delete_all(self):
        # Get all models
        incoming_pager = self.list(get_definition=True)
        incoming_result = []
        try:
            while True:
                incoming_result.extend(incoming_pager.advance_page())
        except StopIteration:
            pass
        except ErrorResponseException as e:
            handle_service_exception(e)

        # Build dict of model_id : set of parent_ids
        parsed_models = {model.id: set() for model in incoming_result}
        for model in incoming_result:
            # Parse dependents, add current model as parent of dependents
            dependencies = get_model_dependencies(model.model)
            for d_id in dependencies:
                parsed_models[d_id].add(model.id)

        def delete_parents(model_id, model_dict):
            # Check if current model has been deleted already
            if model_id not in model_dict:
                return

            # Delete parents first
            for parent_id in model_dict[model_id]:
                if parent_id in model_dict:
                    delete_parents(parent_id, model_dict)

            # Delete current model and remove references
            del model_dict[model_id]
            try:
                self.delete(model_id)
            except Exception as e:
                logger.warning(f"Could not delete model {model_id}; error is {e}")

        while len(parsed_models) > 0:
            model_id = next(iter(parsed_models))
            delete_parents(model_id, parsed_models)


"""
File:   ddpg.py
Author: Nathaniel Hamilton

Description:    This class implements the Deep Deterministic Policy Gradient algorithm written about in
                https://arxiv.org/abs/1509.02971

"""

import time
import gc
import os
import numpy as np
import torch
import torch.optim as optim
from torch.autograd import Variable
import torch.nn.functional as F
# from algorithms.abstract_algorithm import Algorithm
from utils.replay_buffer import *
from utils.ornstein_uhlenbeck_noise import *
from algorithms.ddpg.core import *

class DDPG():
    def __init__(self, runner, num_training_steps, time_per_step, rollout_length=1000, evaluation_length=1000,
                 evaluation_iter=10, num_evaluations=5, random_seed=8, replay_capacity=500, batch_size=100,
                 architecture='standard',
                 actor_learning_rate=1e-4, critic_learning_rate=1e-3, weight_decay=1e-2, gamma=0.99, tau=0.001,
                 noise_sigma=0.2, noise_theta=0.15, log_path='.', save_path='.', load_path=None, render_eval=True):
        """
        This class implements the Deep Deterministic Policy Gradient algorithm written about in
        https://arxiv.org/abs/1509.02971

        The input names are descriptive of variable's meaning, but within the class, the variable names from the paper
        are used.

        :param runner:               (Runner) The interface between the learning agent and the environment.
        :param num_training_steps:   (int)    The total number of steps taken during training. The agent will execute at
                                                least this many steps.
        :param rollout_length:       (int)    The maximum number of steps the agent should take during a rollout.
                                                i.e. how far the agent explores in any given direction.
        :param evaluation_length:    (int)    The maximum number of steps the agent should take when evaluating the
                                                policy.
        :param evaluation_iter:      (int)    The number of exploration iterations completed between policy evaluations.
                                                This is also the point at which the policy is saved.
        :param num_evaluations:      (int)    The number of evaluations that over at an evaluation point. It is best to
                                                complete at lease 3 evaluation traces to account for the variance in
                                                transition probabilities.
        :param random_seed:          (int)    The random seed value to use for the whole training process.
        :param replay_capacity:      (int)    The capacity of the replay buffer. It will never hold more than this many
                                                memories.
        :param batch_size:           (int)    Number of memories to sample for each update.
        TODO: NN architecture info
        :param actor_learning_rate:  (float)  The learning rate for the Adam optimizer updating the actor.
        :param critic_learning_rate: (float)  The learning rate for the Adam optimizer updating the critic.
        :param weight_decay:         (float)  Weight decay for Adam optimizer updating the critic.
        :param gamma:                (float)  Discount factor for computing returns.
        :param tau:                  (float)  Soft update factor.
        :param noise_sigma:          (float)  Standard deviation of exploration noise.
        :param noise_theta:          (float)  Theta parameter for Ornstein-Uhlenbeck exploration noise.
        :param log_path:             (str)    File path to directory where episode_performance.csv will be saved.
        :param save_path:            (str)    File path to directory where all models will be saved.
        :param load_path:            (str)    File path to initial model to be loaded.
        :param render_eval           (bool)   Boolean selection for rendering the environment during evaluation.
        """

        # Save all parameters
        self.runner = runner
        self.is_discrete = runner.is_discrete
        self.H = rollout_length
        self.eval_len = evaluation_length
        self.eval_iter = evaluation_iter
        self.num_evals = num_evaluations
        self.num_training_steps = num_training_steps
        self.save_path = save_path
        self.capacity = replay_capacity
        self.batch_size = batch_size
        self.actor_lr = actor_learning_rate
        self.critic_lr = critic_learning_rate
        self.weight_decay = weight_decay
        self.gamma = gamma
        self.tau = tau
        self.render = render_eval

        # Initialize Cuda variables
        use_cuda = torch.cuda.is_available()
        self.device = torch.device("cuda" if use_cuda else "cpu")

        # Set the random seed
        np.random.seed(random_seed)
        torch.manual_seed(random_seed)
        torch.cuda.manual_seed_all(random_seed)

        # Create the actor neural network and its target
        if architecture == 'verivital':
            self.actor = VerivitalActor(num_inputs=runner.obs_shape[0], hidden_size1=64, hidden_size2=64,
                                        num_actions=runner.action_shape[0], final_bias=3e-3).to(self.device)
            self.actor_target = VerivitalActor(num_inputs=runner.obs_shape[0], hidden_size1=64, hidden_size2=64,
                                               num_actions=runner.action_shape[0], final_bias=3e-3).to(self.device)
        else:
            self.actor = DDPGActor(num_inputs=runner.obs_shape[0], hidden_size1=64, hidden_size2=64,
                                   num_actions=runner.action_shape[0], final_bias=3e-3).to(self.device)
            self.actor_target = DDPGActor(num_inputs=runner.obs_shape[0], hidden_size1=64, hidden_size2=64,
                                          num_actions=runner.action_shape[0], final_bias=3e-3).to(self.device)

        # Create the critic neural network and its target
        self.critic = DDPGCritic(num_inputs=runner.obs_shape[0], hidden_size1=64, hidden_size2=64,
                                 num_actions=runner.action_shape[0], final_bias=3e-3).to(self.device)
        self.critic_target = DDPGCritic(num_inputs=runner.obs_shape[0], hidden_size1=64, hidden_size2=64,
                                        num_actions=runner.action_shape[0], final_bias=3e-3).to(self.device)

        # Create the optimizers for the actor and critic neural networks
        self.actor_optimizer = optim.Adam(self.actor.parameters(), lr=self.actor_lr)
        self.critic_optimizer = optim.Adam(self.critic.parameters(), lr=self.critic_lr,
                                           weight_decay=self.weight_decay)

        # Initialize the target NNs or load models
        if (load_path is None) or (load_path == 'None'):
            # Targets are copied with a hard update
            hard_update(target=self.actor_target, source=self.actor)
            hard_update(target=self.critic_target, source=self.critic)

        else:
            self.load_model(load_path)

        # Create the log files
        if not os.path.isdir(log_path):
            os.makedirs(log_path)
        self.log_save_name = log_path + '/episode_performance.csv'
        f = open(self.log_save_name, "w+")
        f.write("training steps, time, steps in evaluation, accumulated reward, done, exit condition \n")
        f.close()

        # Make sure the save path exists
        if not os.path.isdir(save_path):
            os.mkdir(save_path)

        # Initialize variables used by the learner
        self.replay_buffer = ReplayBuffer(self.capacity)
        self.noise = OrnsteinUhlenbeckActionNoise(mu=np.zeros(runner.action_shape),
                                                  sigma=(noise_sigma * np.ones(runner.action_shape)),
                                                  theta=noise_theta, dt=time_per_step)
        # self.old_params = list(self.actor.parameters())[0].clone()

    def evaluate_model(self, evaluation_length=-1):
        """
        This method performs an evaluation of the model. The evaluation lasts for the specified number of executed
        steps. Multiple evaluations should be used to account for variations in performance.

        :input:
            :param evaluation_length:   (int)   The maximum number of steps to execute during the evaluation run. If -1
                                                    is used, the method will ignore an upper bound for number of steps
                                                    to execute.
        :outputs:
            :return reward_sum:         (float) The cummulative reward collected during the run.
            :return step:               (int)   The number of steps executed during the run.
            :return done:               (int)   A signal indicating if the agent reached a final state.
            :return exit_cond:          (int)   A signal indicating if the agent reached a fatal state.
        """

        # Initialize
        step = 0
        reward_sum = 0

        # Start the evaluation from a safe starting point
        self.runner.reset(evaluate=True)
        state = self.runner.get_state()
        done = 0
        exit_cond = 0

        while self.runner.is_available():
            # Stop the controller if there is a collision or time-out
            if done or exit_cond or (step >= evaluation_length != -1):
                # stop
                self.runner.stop()
                break

            # Determine the next action
            action = self.get_action(state)  # No noise injected during evaluation

            # Execute determined action
            next_state, reward, done, exit_cond = self.runner.step(action, render=self.render)

            # Update for next step
            reward_sum += reward
            state = next_state
            step += 1

        return reward_sum, step, done, exit_cond

    def __explore(self, num_steps):
        """
        Execute random actions exploring the environment updating after each step.

        :param num_steps:   (int)   The number of steps to perform during the exploration.
        """

        # Initialize variables
        steps_taken = 0

        # Get the initial state information and reset the Ornstein-Uhlenbeck noise
        self.noise.reset()
        state = self.runner.get_state()

        # during exploration, update after every step
        while steps_taken < num_steps:

            # Determine the action to take
            action = self.get_action(state, self.noise.noise())

            # Take the action
            next_state, reward, done, exit_cond = self.runner.step(action)

            # Record the information in the replay buffer
            self.replay_buffer.add_memory(state, action, reward, done, exit_cond, next_state)

            # Only update if the replay buffer is full
            if len(self.replay_buffer.rewards) >= self.batch_size:
                # Update the neural networks using a random sampling of the replay buffer
                mb_states, mb_actions, mb_rewards, mb_dones, mb_exits, mb_next_states = self.replay_buffer.sample_batch(
                    self.batch_size)
                self.update_model(mb_states, mb_actions, mb_rewards, mb_dones, mb_exits, mb_next_states)

            # Only reset the environment if a terminal state has been reached
            if done == 1 or exit_cond == 1:
                self.runner.reset()
                self.noise.reset()
                state = self.runner.get_state()
            else:
                state = next_state

            steps_taken += 1
        print('episode completed')
        return

    def get_action(self, state, noise=None):
        """
        This function calculates the desired output using the NN and the exploration noise.

        :input:
            :param state:       (ndarray)   Input state to determine which action to take
            :param noise:       (ndarray)   The exploration noise. Default=[0, 0] (no noise)
        :output:
            :return action:     (ndarray)   The chosen action to take
        """
        # Forward pass the network
        state = torch.FloatTensor(state).to(self.device)
        self.actor.eval()  # Must be in eval mode to execute a forward pass
        action = self.actor.forward(state)
        self.actor.train()  # Must be in train mode to record gradients
        action = action.cpu()

        # Add the process noise to the action
        if noise is not None:
            action = action + torch.FloatTensor(noise).cpu()
            # print(action)
        action = action.clamp(-1.0, 1.0)  # Make sure action cannot exceed limits

        # Convert to numpy array
        np_action = action.detach().numpy()

        return np_action

    def load_model(self, load_path=None):
        """
        This function loads the neural network models and optimizers for both the actor and the critic from one file.
        If a load_path is not provided, the function will not execute. For more examples on how to save/load models,
        visit https://pytorch.org/tutorials/beginner/saving_loading_models.html

        :param load_path:  (string) The file name where the models will be loaded from. default=None
        """

        # Load the saved file as a dictionary
        if load_path is not None:
            checkpoint = torch.load(load_path)

            # Store the saved models
            self.actor.load_state_dict(checkpoint['actor'])
            self.critic.load_state_dict(checkpoint['critic'])
            self.actor_target.load_state_dict(checkpoint['actor_target'])
            self.critic_target.load_state_dict(checkpoint['critic_target'])
            self.actor_optimizer.load_state_dict(checkpoint['actor_optimizer'])
            self.critic_optimizer.load_state_dict(checkpoint['critic_optimizer'])
            self.replay_buffer = ReplayBuffer(self.capacity, checkpoint['replay_buffer'])

            # Evaluate the neural networks to ensure the weights were properly loaded
            self.actor.eval()
            self.critic.eval()
            self.actor_target.eval()
            self.critic_target.eval()

        # Clean up any garbage that's accrued
        gc.collect()

        return

    def save_model(self, save_path='.'):
        """
        This method saves the neural network models and optimizers for both the actor and the critic in one file. For
        more examples on how to save/load models, visit
        https://pytorch.org/tutorials/beginner/saving_loading_models.html

        :input:
            :param save_path: (string) The file name the model will be saved to. Default='model.pth'
        """

        # Save everything necessary to start up from this point
        torch.save({
            'actor': self.actor.state_dict(),
            'critic': self.critic.state_dict(),
            'actor_target': self.actor_target.state_dict(),
            'critic_target': self.critic_target.state_dict(),
            'actor_optimizer': self.actor_optimizer.state_dict(),
            'critic_optimizer': self.critic_optimizer.state_dict(),
            'replay_buffer': self.replay_buffer,
        }, save_path)

        # Clean up any garbage that's accrued
        gc.collect()

        return

    def train_model(self):
        """
        This method trains the agent to learn an optimal model according to the DDPG algorithm.

        :outputs:
            :return final_policy_reward_sum: (float) The accumulated reward collected by the agent evaluated using the
                                                        latest update of the learned policy.
            :return execution_time:          (float) The amount of time (in seconds) it took to run the full
                                                        train_model() method.
            :return training_time:           (float) The amount of time (in seconds) it took to complete the training
                                                        process.
        """

        # Initialize iterative values
        t_start = time.time()
        step = 0
        eval_count = 0
        evaluation_time = 0.0

        # Evaluate once before training
        t_eval_start = time.time()
        log_time = t_eval_start - t_start - evaluation_time
        avg_steps = 0.0
        avg_reward = 0.0
        for j in range(self.num_evals):
            reward, eval_steps, done, exit_cond = self.evaluate_model(self.eval_len)

            # Log the evaluation run
            with open(self.log_save_name, "a") as myfile:
                myfile.write(str(step) + ', ' + str(log_time) + ', ' + str(eval_steps) + ', ' + str(reward) +
                             ', ' + str(done) + ', ' + str(exit_cond) + '\n')

            avg_steps += eval_steps
            avg_reward += reward

        # Print the average results for the user to debugging
        print('Training Steps: ' + str(step) + ', Avg Steps in Episode: ' + str(avg_steps / self.num_evals) +
              ', Avg Acc Reward: ' + str(avg_reward / self.num_evals))

        # Save the model that achieved this performance
        print('saving...')
        save_path = self.save_path + '/step_' + str(step) + '_model.pth'
        self.save_model(save_path=save_path)

        # Do not count the time taken to evaluate and save the model as training time
        t_eval_end = time.time()
        evaluation_time += t_eval_end - t_eval_start
        self.runner.reset()

        # Iterate through a sufficient number of steps broken into horizons
        while step < self.num_training_steps:

            # Train through exploration
            ep_length = min(self.H, (self.num_training_steps - step))
            self.__explore(ep_length)
            step += ep_length

            # Evaluate the model
            eval_count += 1
            if eval_count % self.eval_iter == 0:
                t_eval_start = time.time()
                log_time = t_eval_start - t_start - evaluation_time
                avg_steps = 0.0
                avg_reward = 0.0
                for j in range(self.num_evals):
                    reward, eval_steps, done, exit_cond = self.evaluate_model(self.eval_len)

                    # Log the evaluation run
                    with open(self.log_save_name, "a") as myfile:
                        myfile.write(str(step) + ', ' + str(log_time) + ', ' + str(eval_steps) + ', ' + str(reward) +
                                     ', ' + str(done) + ', ' + str(exit_cond) + '\n')

                    avg_steps += eval_steps
                    avg_reward += reward

                # Print the average results for the user to debugging
                print('Training Steps: ' + str(step) + ', Avg Steps in Episode: ' + str(avg_steps / self.num_evals) +
                      ', Avg Acc Reward: ' + str(avg_reward / self.num_evals))

                # Save the model that achieved this performance
                print('saving...')
                save_path = self.save_path + '/step_' + str(step) + '_model.pth'
                self.save_model(save_path=save_path)

                # Do not count the time taken to evaluate and save the model as training time
                t_eval_end = time.time()
                evaluation_time += t_eval_end - t_eval_start

        t_train = time.time()
        training_time = t_train - t_start - evaluation_time

        # Evaluate and save the final learned model
        final_policy_reward_sum, _, _, _ = self.evaluate_model(self.eval_len)
        print('saving...')
        save_path = self.save_path + '/final_model.pth'
        self.save_model(save_path=save_path)

        t_final = time.time()
        execution_time = t_final - t_start

        return final_policy_reward_sum, execution_time, training_time

    def update_model(self, states, actions, rewards, dones, exits, next_states):
        """
        This function updates neural networks for the actor and critic using back-propogation. More information about
        this process can be found in the DDPG paper.

        :inputs:
            :param states:       (list)  The batch of states from the replay buffer
            :param actions:      (list)  The batch of actions from the replay buffer
            :param rewards:      (list)  The batch of rewards from the replay buffer
            :param dones:        (list)  The batch of done values (1 indicates done, 0 indicates not done) from the
                                           replay buffer
            :param exits:        (list)  The batch of exit conditions (1 indicates exited early, 0 indicates normal)
                                           from the replay buffer
            :param next_states:  (list)  The batch of states reached after executing the actions from the replay buffer
        :outputs:
            :return actor_loss:  (float) The loss value calculated for the actor
            :return critic_loss: (float) The loss value calculated for the critic
        """
        # Convert the inputs into tensors to speed up computations
        batch_states = Variable(torch.FloatTensor(states).to(self.device), requires_grad=True)
        batch_actions = Variable(torch.FloatTensor(actions).to(self.device), requires_grad=True)
        batch_rewards = torch.FloatTensor(rewards).to(self.device).unsqueeze(1)
        batch_dones = torch.FloatTensor(dones).to(self.device).unsqueeze(1)
        batch_exits = torch.FloatTensor(exits).to(self.device).unsqueeze(1)
        batch_next_states = torch.FloatTensor(next_states).to(self.device)

        # Compute the critics estimated Q values
        # print(batch_actions)
        batch_qs = self.critic.forward(batch_states, batch_actions)

        # Compute what the actions would have been without noise
        actions_without_noise = self.actor.forward(batch_states)

        # Compute the target's next state and next Q-value estimates used for computing loss
        target_next_action_batch = (self.actor_target.forward(batch_next_states)).detach()
        target_next_q_batch = (self.critic_target.forward(batch_next_states, target_next_action_batch)).detach()

        # Compute y (a metric for computing the critic loss)
        y = (batch_rewards + ((1 - batch_dones) * (1 - batch_exits) * self.gamma * target_next_q_batch)).detach()

        # Compute critic loss and update using the optimizer
        self.critic_optimizer.zero_grad()
        critic_loss = F.mse_loss(y, batch_qs)
        critic_loss.backward()
        self.critic_optimizer.step()

        # Compute the actor loss and update using the optimizer
        self.actor_optimizer.zero_grad()
        actor_loss = -self.critic(batch_states, actions_without_noise)
        actor_loss = actor_loss.mean()
        actor_loss.backward()
        self.actor_optimizer.step()

        # Update the target networks
        soft_update(target=self.actor_target, source=self.actor, tau=self.tau)
        soft_update(target=self.critic_target, source=self.critic, tau=self.tau)

        # new_params = list(self.actor.parameters())[0].clone()
        # print(torch.equal(new_params.data, self.old_params.data))
        #
        # self.old_params = new_params

        # Output the loss values for logging purposes
        return actor_loss.cpu(), critic_loss.cpu()


# -*- encoding: utf-8 -*-
from ..component_utils import dateformat
from datetime import datetime, timedelta

class TableInvoices:
    def __init__(self):
        pass

    def get_origen_lectura(self, cursor, uid, lectura):
        """Busquem l'origen de la lectura cercant-la a les lectures de facturació"""
        res = {lectura.data_actual: '',
            lectura.data_anterior: ''}

        lectura_obj = lectura.pool.get('giscedata.lectures.lectura')
        tarifa_obj = lectura.pool.get('giscedata.polissa.tarifa')
        origen_obj = lectura.pool.get('giscedata.lectures.origen')
        origen_comer_obj = lectura.pool.get('giscedata.lectures.origen_comer')

        estimada_id = origen_obj.search(cursor, uid, [('codi', '=', '40')])[0]
        sin_lectura_id = origen_obj.search(cursor, uid, [('codi', '=', '99')])[0]
        estimada_som_id = origen_comer_obj.search(cursor, uid, [('codi', '=', 'ES')])[0]
        calculada_som_id = origen_obj.search(cursor, uid, [('codi', '=', 'LC')])
        calculada_som_id = calculada_som_id[0] if calculada_som_id else None

        #Busquem la tarifa
        tarifa_id = tarifa_obj.search(cursor, uid, [('name', '=', lectura.name[:-5])])
        if tarifa_id:
            tipus = lectura.tipus == 'activa' and 'A' or 'R'

            search_vals = [('comptador', '=', lectura.comptador),
                        ('periode.name', '=', lectura.name[-3:-1]),
                        ('periode.tarifa', '=', tarifa_id[0]),
                        ('tipus', '=', tipus),
                        ('name', 'in', [lectura.data_actual,
                                        lectura.data_anterior])]
            lect_ids = lectura_obj.search(cursor, uid, search_vals)
            lect_vals = lectura_obj.read(cursor, uid, lect_ids,
                                        ['name', 'origen_comer_id', 'origen_id'])
            for lect in lect_vals:
                # En funció dels origens, escrivim el text
                # Si Estimada (40) o Sin Lectura (99) i Estimada (ES): Estimada Somenergia
                # Si Estimada (40) o Sin Lectura (99) i F1/Q1/etc...(!ES): Estimada distribuïdora
                # La resta: Real
                origen_txt = "real"
                if lect['origen_id'][0] in [ estimada_id, sin_lectura_id ]:
                    if lect['origen_comer_id'][0] == estimada_som_id:
                        origen_txt = "calculada per Som Energia"
                    else:
                        origen_txt = "estimada distribuïdora"
                if lect['origen_id'][0] == calculada_som_id:
                    origen_txt = "calculada segons CCH"
                res[lect['name']] = "%s" % (origen_txt)

        return res

    def get_data(self, cursor, uid, wiz, invoice_ids, context={}):
        result = {}
        result['type'] = 'TableInvoices'
        result['taula'] = []
        result['date_from'] = False
        result['date_to'] = False
        fact_obj = wiz.pool.get('giscedata.facturacio.factura')
        for invoice_id in invoice_ids:
            invoice = fact_obj.browse(cursor, uid, invoice_id)
            if invoice.type in ('out_invoice', 'out_refund') and invoice.state in ('paid', 'open'):
                linia_taula = {}
                linia_taula['invoice_number'] = invoice.number
                linia_taula['date'] = dateformat(invoice.date_invoice)
                linia_taula['date_from'] = dateformat(invoice.data_inici)
                if invoice.data_inici:
                    if not result['date_from'] or datetime.strptime(invoice.data_inici,'%Y-%m-%d') < datetime.strptime(result['date_from'],'%d-%m-%Y'):
                        result['date_from'] = dateformat(invoice.data_inici)
                linia_taula['date_to'] = dateformat(invoice.data_final)
                if invoice.data_final:
                    if not result['date_to'] or  datetime.strptime(invoice.data_final,'%Y-%m-%d') > datetime.strptime(result['date_to'],'%d-%m-%Y'):
                        result['date_to'] = dateformat(invoice.data_final)
                linia_taula['max_power'] = invoice.potencia_max or 0
                linia_taula['invoiced_energy'] = invoice.energia_kwh or 0
                linia_taula['exported_energy'] = invoice.generacio_kwh or 0
                linia_taula['origin'] = self.get_invoice_origin(cursor, uid, invoice)
                linia_taula['invoiced_days'] = invoice.dies or 0
                linia_taula['total'] = invoice.signed_amount_total
                result['taula'].append(linia_taula)
        return result

    def get_invoice_origin(self, cursor, uid, invoice):
        readings = {}
        lectures = invoice.lectures_energia_ids
        if lectures != None:
            for lectura in lectures:
                origens = self.get_origen_lectura(cursor, uid,lectura)
                if '(P1)' in lectura.name:
                    data = str(datetime.strptime(lectura.data_anterior, '%Y-%m-%d').date() + timedelta(days=1))
                    origin = (u'estimada')
                    if origens[lectura.data_anterior] == 'real' and origens[lectura.data_actual] =='real':
                        origin = (u'real')
                    elif (origens[lectura.data_anterior] == 'estimada distribuïdora' or origens[lectura.data_anterior] == 'real') and origens[lectura.data_actual] == 'calculada segons CCH':
                        origin = (u'calculada')
                    elif origens[lectura.data_anterior] == 'calculada segons CCH' and (origens[lectura.data_actual] =='calculada segons CCH' or origens[lectura.data_actual] == 'real'):
                        origin = (u'calculada')

                    readings[data] = origin

            return readings[invoice.data_inici] if invoice.data_inici in readings else (u'sense lectura')


#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
stemAQ_fit2.py

1. construct 3D wheat stem
2. perform ray tracing simulation
3. fit AQ curve
"""

import argparse
import time
import numpy as np
import math
import re
import random
import copy
import os
import sys
import json
import subprocess

import matplotlib.pyplot as plt
#from scipy.interpolate import pchip
from scipy.interpolate import PchipInterpolator
from scipy.optimize import leastsq,curve_fit,fmin_slsqp

global epsilon
epsilon=0.0001

# interpolant = PchipInterpolator(x, y)
# xnew = np.linspace(0, 10, num=41, endpoint=True)
# interpolant(xnew)

__author__="CHANG Tiangen"
__date__="20201215"
__copyright__="Copyright 2020 Aspar CHANG"
__license__="SIPPE"
__version__="0.1"
__email__="changtiangen@sippe.ac.cn"
__status__="Prototype"
__credits__=[]

class Logger(object):
    def __init__(self, filename="Default.log"):
        self.terminal = sys.stdout
        self.log = open(filename, "a")
    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)
    def flush(self):
        pass

def createHelp():
    """
    Create the command line interface of the programme.
    """

    epilog_string="Any bug is welcome reported to changtiangen@sippe.ac.cn"
    description_string='The program is going to fit the wheat AQ curve.'
    parser = argparse.ArgumentParser(description=description_string,epilog=epilog_string)
    parser.add_argument('-i', '--fnIn', dest='file_in', help='Input stem basic info file')
    parser.add_argument('-iw', '--fnIn_w', dest='file_in_w', help='Input weather data file')
    parser.add_argument('-o1', '--fnOut1', dest='file_out1', help='Output structure file')
    parser.add_argument('-o2', '--fnOut2', dest='file_out2', help='Output AQ parameter file')
    parser.add_argument('-rID', '--runID', dest='runID', type=int, help='Remark of current run ID, used as file prefix and random seed.')
    parser.add_argument('-log', '--printInfo2logFile', dest='log', type=int, default=0, help='Log printed info to log file (1) or not (0).')
    parser.add_argument('-ow', '--overWrite', dest='ow', type=int, default=1, help='Overwrite the existing files (1) or not (0).')
    parser.add_argument('-rd', '--rayDense', dest='RD', type=float, default=0.05, help='Ray density (cm).')
    parser.add_argument('-show', '--show', dest='showOn', type=int, default=0, help='Show 3D structure on screen (1) or not (0).')
    op=parser.parse_args()
    return op

def rayFileMerge(M_plant_ray_fn_list,M_plant_ray_fn,ave_num):
    data1_raw=open(M_plant_ray_fn_list[0],'r').readlines()
    col_num=len(data1_raw[0].strip().split('\t'))
    row_num=len(data1_raw)
    data_clean=np.zeros([row_num-1,col_num])
    for i in range(1,row_num):
        line=data1_raw[i].replace('-nan(ind)','0')
        words=line.strip().split('\t')
        if len(words)>1:
            data_clean[i-1]=[float(c) for c in words]
    for file_i in range(1,len(M_plant_ray_fn_list)):
        data1_raw=open(M_plant_ray_fn_list[file_i],'r').readlines()
        for i in range(1,row_num):
            line=data1_raw[i].replace('-nan(ind)','0')
            words=line.strip().split('\t')
            if len(words)>1:
                PPFDs=[float(c) for c in words[18:]]
                data_clean[i-1][18:]=data_clean[i-1][18:]+PPFDs
    if ave_num>1:
        data_clean[:,18:]=data_clean[:,18:]/ave_num
    fnOut=open(M_plant_ray_fn,'w')
    fnOut.write(data1_raw[0])
    for i in range(row_num-1):
        content1='\t'.join([str(round(c)) for c in data_clean[i][0:5]])
        content2='\t'.join([str(c) for c in data_clean[i][5:]])
        content=content1+'\t'+content2+'\n'
        fnOut.write(content)
    fnOut.close()
    return data_clean

def uniqueFitJudger(itera_test_in,fit_error_new,fit_error,popt,popt_old,uniq_old):
    itera_test_out=itera_test_in
    fit_error_out=fit_error
    popt_new=popt_old
    #print(popt_new,popt_old)
    theta,Amax25,phi_CO2,Rd25=popt_old
    theta_uniq,Amax25_uniq,phi_CO2_uniq,Rd25_uniq=uniq_old
    if fit_error_new<fit_error-epsilon:
        print('fit_error_new: %.2f'%fit_error_new)
        popt_new=popt
        fit_error_out=fit_error_new
        itera_test_out=1
        theta_uniq='YES'
        Amax25_uniq='YES'
        phi_CO2_uniq='YES'
        Rd25_uniq='YES'
    elif fit_error_new>fit_error+epsilon:
        True
    else:
        if abs(theta-popt[0])/(theta+epsilon)>0.05:
            theta_uniq='NO'
        if abs(Amax25-popt[1])/(Amax25+epsilon)>0.05:
            Amax25_uniq='NO'
        if abs(phi_CO2-popt[2])/(phi_CO2+epsilon)>0.05:
            phi_CO2_uniq='NO'
        if abs(Rd25-popt[3])/(Rd25+epsilon)>0.05:
            Rd25_uniq='NO'
    uniq_new=[theta_uniq,Amax25_uniq,phi_CO2_uniq,Rd25_uniq]
    return itera_test_out,fit_error_out,popt_new,uniq_new

def fit_AQ(facet_area_array,facet_PPFD_array,PAR_ave,A_ave,CT_ave,Amax_coef,Rd_coef):
    x=np.array(PAR_ave)
    y=np.array(A_ave)
    z=np.array(CT_ave)
    tot_PPF=np.dot(facet_PPFD_array,facet_area_array)/10 # nmol s-1
    x_tot=np.array(PAR_ave)/2000*tot_PPF
    ################ simple total absorbed PPFD to A AQ fitting ################
    itera_test=0
    fit_error=100000000
    theta=0
    Amax25=0
    phi_CO2=0
    Rd25=0
    theta_uniq='YES'
    Amax25_uniq='YES'
    phi_CO2_uniq='YES'
    Rd25_uniq='YES'

    while itera_test < 50:
        print('            Simple AQ fitting: iteration %d, fit_error %.1f'%(itera_test,fit_error))
        theta_ini=random.uniform(0.01,0.999)
        Amax25_ini=random.randint(1,100)*1.01
        phi_CO2_ini=random.uniform(0,0.1)
        Rd25_ini=random.randint(1,100)*1.01
        paras0=np.array([theta_ini,Amax25_ini,phi_CO2_ini,Rd25_ini]) # theta,Amax,alpha,Rd
        popt= fmin_slsqp(fit_residuals, paras0, bounds=[(0.01,0.999),(0,101),(0,0.1),(0,101)], args=(x_tot,y,z,Amax_coef,Rd_coef),iprint = 0)
        popt=popt[0:]
        if math.isnan(popt[0]):
            continue
        if theta+Amax25+phi_CO2==0:
            theta,Amax25,phi_CO2,Rd25=popt
            fit_error=fit_residuals(popt[0:],x_tot,y,z,Amax_coef,Rd_coef)
        else:
            fit_error_new=fit_residuals(popt[0:],x_tot,y,z,Amax_coef,Rd_coef)
            popt_old=[theta,Amax25,phi_CO2,Rd25]
            uniq_old=[theta_uniq,Amax25_uniq,phi_CO2_uniq,Rd25_uniq]
            itera_test,fit_error,popt_new,uniq_new = uniqueFitJudger(itera_test,fit_error_new,fit_error,popt,popt_old,uniq_old)
            theta,Amax25,phi_CO2,Rd25=popt_new
            theta_uniq,Amax25_uniq,phi_CO2_uniq,Rd25_uniq=uniq_new
        itera_test=itera_test+1
    Rd_array=Rd25*(Rd_coef[0]*z**2 + Rd_coef[1]*z + Rd_coef[2]) # Rd-T empirical relation
    Amax_array=Amax25*(Amax_coef[0]*z**2 + Amax_coef[1]*z + Amax_coef[2]) # Rd-T empirical relation
    Ag_max=Amax_array+Rd_array
    A_fit_array=(phi_CO2*x_tot+Ag_max-np.sqrt((phi_CO2*x_tot+Ag_max)**2-4*theta*phi_CO2*x_tot*Ag_max))/(2*theta)-Rd_array

    R2_fit=1-sum((A_fit_array-y)**2)/sum((y-np.mean(y))**2)
    result_1=[theta,Amax25,phi_CO2,Rd25,theta_uniq,Amax25_uniq,phi_CO2_uniq,Rd25_uniq,fit_error,R2_fit,A_fit_array]

    ################ Ray tracing PAR_on_facet VS A_ave AQ fitting ################
    itera_test=0
    fit_error=100000000
    theta=0
    Amax25=0
    phi_CO2=0
    Rd25=0
    theta_uniq='YES'
    Amax25_uniq='YES'
    phi_CO2_uniq='YES'
    Rd25_uniq='YES'
    Amax25_ub=20
    Rd25_ub=10
    while itera_test < 50:
        print('            Ray tracing AQ fitting: iteration %d, fit_error %.1f'%(itera_test,fit_error))
        theta_ini=random.uniform(0.01,0.999)
        Amax25_ini=random.uniform(0,Amax25_ub)
        phi_CO2_ini=random.uniform(0,0.1)
        Rd25_ini=random.uniform(0,Rd25_ub)
#         theta_ini=0.839100165
#         Amax_ini=4.093976743
#         phi_CO2_ini=0.079715078
#         Rd25_ini=2.420171864
        paras0=np.array([theta_ini,Amax25_ini,phi_CO2_ini,Rd25_ini]) # theta,Amax,alpha,Rd
        popt= fmin_slsqp(fit_residuals2, paras0, bounds=[(0.01,0.999),(0,Amax25_ub),(0,0.1),(0,Rd25_ub)], args=(facet_area_array,facet_PPFD_array,x,y,z,Amax_coef,Rd_coef),iprint = 0)
        popt=popt[0:]
        if math.isnan(popt[0]):
            continue
        if theta+Amax25+phi_CO2==0:
            theta,Amax25,phi_CO2,Rd25=popt
            fit_error=fit_residuals2(popt[0:],facet_area_array,facet_PPFD_array,x,y,z,Amax_coef,Rd_coef)
        else:
            fit_error_new=fit_residuals2(popt[0:],facet_area_array,facet_PPFD_array,x,y,z,Amax_coef,Rd_coef)
            popt_old=[theta,Amax25,phi_CO2,Rd25]
            uniq_old=[theta_uniq,Amax25_uniq,phi_CO2_uniq,Rd25_uniq]
            itera_test,fit_error,popt_new,uniq_new = uniqueFitJudger(itera_test,fit_error_new,fit_error,popt,popt_old,uniq_old)
            theta,Amax25,phi_CO2,Rd25=popt_new
            theta_uniq,Amax25_uniq,phi_CO2_uniq,Rd25_uniq=uniq_new
        itera_test=itera_test+1
    AQ_paras=[theta,Amax25,phi_CO2,Rd25]
    A_fit_array2=Aspike_fit(facet_area_array,facet_PPFD_array,AQ_paras,x,z,Amax_coef,Rd_coef)
    R2_fit=1-sum((A_fit_array2-y)**2)/sum((y-np.mean(y))**2)
    result_2=[theta,Amax25,phi_CO2,Rd25,theta_uniq,Amax25_uniq,phi_CO2_uniq,Rd25_uniq,fit_error,R2_fit,A_fit_array2]

    return x_tot,result_1,result_2

def Aspike_fit(facet_area_array,facet_PPFD_array,AQ_paras,PAR_ave,CT_ave,Amax_coef,Rd_coef):
    theta,Amax25,phi_CO2,Rd25=AQ_paras
    y_fit=[]
    for PAR_i in range(len(PAR_ave)):
        x=facet_PPFD_array*PAR_ave[PAR_i]/2000
        Rd=Rd25*(Rd_coef[0]*CT_ave[PAR_i]**2 + Rd_coef[1]*CT_ave[PAR_i] + Rd_coef[2]) # Rd-T empirical relation
        Amax=Amax25*(Amax_coef[0]*CT_ave[PAR_i]**2 + Amax_coef[1]*CT_ave[PAR_i] + Amax_coef[2]) # Amax-T empirical relation
        Ag_max=Amax+Rd
        A_fit_facet_array=(phi_CO2*x+Ag_max-np.sqrt((phi_CO2*x+Ag_max)**2-4*theta*phi_CO2*x*Ag_max))/(2*theta)-Rd
        y_fit.append(float(np.dot(facet_area_array,A_fit_facet_array)/10)) # nmol ear-1 s-1
    return np.array(y_fit)

def fit_residuals(paras,x_tot,y,z,Amax_coef,Rd_coef):#y=A_entire, x=PPFD, z=CT, y2=A_noAwn, z2=CT2
    theta,Amax25,phi_CO2,Rd25=paras
    Rd_facet_array=Rd25*(Rd_coef[0]*z**2 + Rd_coef[1]*z + Rd_coef[2]) # Rd-T empirical relation
    Amax_facet_array=Amax25*(Amax_coef[0]*z**2 + Amax_coef[1]*z + Amax_coef[2]) # A-T empirical relation
    Ag_facet_max=Amax_facet_array+Rd_facet_array

    sqrt1=(phi_CO2*x_tot+Ag_facet_max)**2-4*theta*phi_CO2*x_tot*Ag_facet_max
    y_fit=(phi_CO2*x_tot+Ag_facet_max-np.sqrt(sqrt1))/(2*theta)-Rd_facet_array
    fit_err=sum((y_fit-y)**2)
    #print('Simple fit: ',y_fit,y_entire)
    return fit_err

def fit_residuals2(paras,facet_area_array,facet_PPFD_array,PAR_ave,A_ave,CT_ave,Amax_coef,Rd_coef):
    theta,Amax25,phi_CO2,Rd25=paras
    Rd_facet_array=Rd25*(Rd_coef[0]*CT_ave**2 + Rd_coef[1]*CT_ave + Rd_coef[2]) # Rd-T empirical relation
    Amax_facet_array=Amax25*(Amax_coef[0]*CT_ave**2 + Amax_coef[1]*CT_ave + Amax_coef[2]) # A-T empirical relation
    y_fit=[]
    for PAR_i in range(len(PAR_ave)):
        x_facet=facet_PPFD_array*(PAR_ave[PAR_i]/2000)
        Rd=Rd_facet_array[PAR_i]
        Amax=Amax_facet_array[PAR_i]
        Ag_max=Amax+Rd
        sqrt1=(phi_CO2*x_facet+Ag_max)**2-(4*theta*phi_CO2*Ag_max)*x_facet
        A_fit_array=(phi_CO2*x_facet+Ag_max-np.sqrt(sqrt1))/(2*theta)-Rd
        y_fit.append(float(np.dot(facet_area_array,A_fit_array)/10)) # nmol ear-1 s-1
    #print('rayTrace fit: ',y_fit,A_entire)
    fit_err=sum((np.array(y_fit)-A_ave)**2)
    return fit_err

def stemReconstructor(M_plant_raw_fn,runID,stemLen,stemDiameter,stemN,overWrite):
    # Canopy structure reconstructing
    print('    Stem structure reconstructing ...\n')
    command='python .\pchamberStemConstructor.py -o %s -rID %d -ow %d -log 1 -sl %.2f -sd %.3f -n %.2f'%(M_plant_raw_fn,runID,overWrite,stemLen,stemDiameter,stemN)
    try:
        subprocess.run(command, check = True)
    except:
        raise Exception('Command execution error: %s'%command)

def plantDrawer(M_plant_fn,plant3D_fn,showOn):
    print('    Stem structure plotting ...\n')
    command='python .\plantDraw3D.py -i %s -o %s -ccs 4 -cql 0 -cqu 1 -cm plasma -alpha 1 -va1 60 -show %d'%(M_plant_fn,plant3D_fn,showOn)
    try:
        subprocess.run(command, check = True)
    except:
        raise Exception('Command execution error: %s'%command)

def PChamberRayTracer(rayDense,M_plant2_fn,M_plant_ray2_fn,M_plant1_fn,M_plant_ray1_fn,fn_weather,DOY):
    print('P-Chamber stem ray tracing. Face A ...\n')
    command='.\\fastTracerVS2019_0.exe -L 31 -d %d -D -0.01 29.01 -2.01 2.01 0 5.02 -S 12 -n %f -W 6 2 7 -m %s -o %s -C %s -s 1'%(DOY,rayDense,M_plant1_fn,M_plant_ray1_fn,fn_weather)
    try:
        subprocess.run(command, check = True)
    except:
        raise Exception('Command execution error: %s'%command)
    print('P-Chamber stem ray tracing. Face B ...\n')
    command='.\\fastTracerVS2019_0.exe -L 31 -d %d -D -0.01 29.01 -2.01 2.01 0 5.02 -S 12 -n %f -W 6 2 7 -m %s -o %s -C %s -s 1'%(DOY,rayDense,M_plant2_fn,M_plant_ray2_fn,fn_weather)
    try:
        subprocess.run(command, check = True)
    except:
        raise Exception('Command execution error: %s'%command)

def file_rename(fnOut_str,runID):
    fnOut_list=fnOut_str.split('\\')
    resultDir='\\'.join(fnOut_list[0:-1])
    if not resultDir:
        resultDir='.'
    if not os.path.exists(resultDir):
        print('  Result directory not exists, creating it ...')
        try:
            os.mkdir(resultDir)
        except:
            raise Exception('Cannot create result directory: %s'%resultDir)
    if 'Run' not in fnOut_list[-1]:
        fnOut_list[-1]='Run'+str(runID)+'_'+fnOut_list[-1]
    fnOut='\\'.join(fnOut_list)
    return fnOut

if __name__=="__main__":
    start=time.time()
    op=createHelp()
    log2file=op.log
    if log2file:
        sys.stdout = Logger('./log_stemAQ_fit')
    width_format=94
    welcome_info='Welcome to use stem AQ fitter.'
    time_info=time.asctime( time.localtime(time.time()) )
    print(welcome_info.center(width_format, '-'))
    print(time_info.center(width_format, ' '))

    runID=op.runID
    random.seed(runID)
    fn_weather=op.file_in_w
    fnIn_spikeInfo=op.file_in
    fnOut_str1=op.file_out1
    fnOut_str2=op.file_out2
    overWrite=op.ow
    rayDense=op.RD
    showOn=op.showOn

    fnOut1=file_rename(fnOut_str1,runID)
    fnOut=file_rename(fnOut_str2,runID)

    op.file_out1=fnOut1
    op.file_out2=fnOut
    data = json.dumps(vars(op), indent=4,ensure_ascii=False, sort_keys=False,separators=(',', ':'))
    print('Running parameters:')
    print(data)

    Rd_coef=[0,0,1]
    Amax_coef=[0,0,1]
    CT_list=[25]
    for line in open(fnIn_spikeInfo,'r').readlines():
        words=line.strip().split('\t')
        if words[0]=='PPFD':
            PPFD_list=[int(c) for c in words[1:]]
        elif words[0]=='A':
            A_list=[float(c) for c in words[1:]] # morning
        elif words[0]=='CT':
            CT_list=[float(c) for c in words[1:]] # morning
        elif words[0]=='stemLen':
            stemLen=float(words[1])
        elif words[0]=='stemDiameter':
            stemDiameter=float(words[1])*1.047 # *1.047 to ensure Perimeter of hexagon equals to circle (area ratio 91%)
        elif words[0]=='stemN':
            stemN=float(words[1])
        elif words[0]=='coef_stem_Asat_airT':
            Amax_coef=[float(c) for c in words[1:]]
        elif words[0]=='coef_stem_Rd_airT':
            Rd_coef=[float(c) for c in words[1:]]
    if len(CT_list)==1:
        CT_list=CT_list*len(PPFD_list)
    if (len(PPFD_list)!=len(A_list)) or (len(PPFD_list)!=len(CT_list)):
        raise Exception('ERROR: length of PPFD_list, A_list and CT_list not match in file: %s'%fnIn_spikeInfo)

    line=open(fn_weather,'r').readline()
    words=line.strip().split('\t')
    DOY=int(words[1])

    fnOut0=fnOut1[0:-4]+'_ray.txt'
    M_plant_raw_fn=fnOut1

    ray_file_list=[]
    M_plant_fn=M_plant_raw_fn[0:-4]+'.txt'
    M_plant1_fn=M_plant_fn[0:-4]+'_1.txt'
    M_plant2_fn=M_plant_fn[0:-4]+'_2.txt'
    plant3D_1_fn=M_plant_fn[0:-4]+'_1_04-060_045.png'
    M_plant_ray1_fn=M_plant_fn[0:-4]+'_1_ray.txt'
    M_plant_ray2_fn=M_plant_fn[0:-4]+'_2_ray.txt'
    M_plant_ray_fn=M_plant_fn[0:-4]+'_ray.txt'

    ray_file_list.append(M_plant_ray_fn)
    if not overWrite:
        print('Ray tracing ...')
        if not os.path.exists(M_plant_ray_fn):
            if not (os.path.exists(M_plant_ray1_fn) and os.path.exists(M_plant_ray2_fn)):
                if not (os.path.exists(M_plant1_fn) and os.path.exists(M_plant2_fn)):
                    stemReconstructor(M_plant_raw_fn,runID,stemLen,stemDiameter,stemN,overWrite)
                if not os.path.exists(plant3D_1_fn):
                    plantDrawer(M_plant1_fn,plant3D_1_fn,showOn)
                PChamberRayTracer(rayDense,M_plant2_fn,M_plant_ray2_fn,M_plant1_fn,M_plant_ray1_fn,fn_weather,DOY)
            print('Merge ray tracing result of face A and B ...')
            rayFileMerge([M_plant_ray1_fn,M_plant_ray2_fn],M_plant_ray_fn,1)
    else: # overwrite all possible existing files
        print('Ray tracing ...')
        stemReconstructor(M_plant_raw_fn,runID,stemLen,stemDiameter,stemN,overWrite)
        plantDrawer(M_plant1_fn,plant3D_1_fn,showOn)
        PChamberRayTracer(rayDense,M_plant2_fn,M_plant_ray2_fn,M_plant1_fn,M_plant_ray1_fn,fn_weather,DOY)
        rayFileMerge([M_plant_ray1_fn,M_plant_ray2_fn],M_plant_ray_fn,1)
    print('Generating merged ray files: %s'%(fnOut0))
    rayData_ave=rayFileMerge(ray_file_list,fnOut0,len(ray_file_list))
    rayData_plant_ave=rayData_ave[rayData_ave[:,2]>-6,:] # only keep plant tissues (forbid ground and walls)
    facet_area1_array=rayData_plant_ave[:,17] # grain area
    facet_PPFD1_array=rayData_plant_ave[:,24] # grain PPFD
    print('Fitting AQ parameters ...')
    #raise Exception(A_list,CT_list,Amax_coef,Rd_coef)
    tot_PPF_stem,fit_simple,fit_rayTrace=fit_AQ(facet_area1_array,facet_PPFD1_array,PPFD_list,A_list,CT_list,Amax_coef,Rd_coef)
    tot_area=sum(facet_area1_array)
    mean_PPFD_max=tot_PPF_stem[0]/tot_area*10
    print('Stem surface area (cm2): %.2f. '%tot_area)
    print('Stem average PPFD (umol/m2/s): %d. '%mean_PPFD_max)
    print('Ray tracing fit result: ')
    print('    theta,Amax,phi_CO2,Rd,theta_uniq,Amax_uniq,phi_CO2_uniq,fit_error,R2_fit: ',fit_rayTrace[0:-1])

    fnOut=open(fnOut,'w')
    fnOut.write('Description\ttheta\tAmax25\tAmax25_net\tphi_CO2\tRd25\ttheta_uniq\tAmax25_uniq\tphi_CO2_uniq\tRd_uniq\tfit_error\tR2_fit\n')
    content=[fit_rayTrace[0]]+[fit_rayTrace[1]+fit_rayTrace[3]]+fit_rayTrace[1:-1]
    fnOut.write('rayTracing_fit\t'+'\t'.join([str(c) for c in content])+'\n')
    fnOut.write('Surface_area(cm2)\t'+str(round(tot_area,2))+'\n')
    fnOut.write('PPFD_ave(umol/m2/s)\t'+str(round(mean_PPFD_max,0))+'\n')
    fnOut.write('PPFD_in(umol/m2/s)\t'+'\t'.join([str(c) for c in PPFD_list])+'\n')
    fnOut.write('A_true(nmol/s)\t'+'\t'.join([str(c) for c in A_list])+'\n')
    fnOut.write('tot_PPF_stem(nmol/s)\t'+'\t'.join([str(c) for c in tot_PPF_stem])+'\n')
    fnOut.write('rayTracing_fitA\t'+'\t'.join([str(round(c,2)) for c in fit_rayTrace[-1]])+'\n')

    fnOut.close()
    eclipse=time.time()-start
    print('stemAQ fitting done! Time used: %.1f s.'%eclipse)