# ---
# jupyter:
#   jupytext:
#     formats: ipynb,py:light
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.6.0
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# + [markdown] papermill={"duration": 0.012225, "end_time": "2019-10-28T22:48:54.094811", "exception": false, "start_time": "2019-10-28T22:48:54.082586", "status": "completed"} tags=[]
# # Evaluators
#
# This notebook covers how to use the three BESOS [Evaluators](https://besos.readthedocs.io/en/latest/evaluator.html):
# - the EnergyPlus Evaluator (`EvaluatorEP`)
# - the Generic Evaluator (`EvaluatorGeneric`)
# - the Energy Hub Evaluator (`EvaluatorEH`)

# + papermill={"duration": 1.005274, "end_time": "2019-10-28T22:48:55.109585", "exception": false, "start_time": "2019-10-28T22:48:54.104311", "status": "completed"} tags=[]
import numpy as np
import pandas as pd

from besos.evaluator import EvaluatorEP, EvaluatorGeneric, EvaluatorEH
from besos import eppy_funcs as ef
from besos import pyehub_funcs as pf
from besos.parameters import FieldSelector, Parameter, PathSelector
from besos.problem import EPProblem, Problem, EHProblem
from besos import sampling


# + [markdown] papermill={"duration": 0.006035, "end_time": "2019-10-28T22:48:55.122661", "exception": false, "start_time": "2019-10-28T22:48:55.116626", "status": "completed"} tags=[]
# ## EvaluatorEP
# The EnergyPlus evaluator needs a problem with parameters that can modify it, objectives to report, and a building model.
# A problem (`problem = parameters + objectives`) can be easily applied to any building model (`evaluator = problem + building`).

# + papermill={"duration": 0.017667, "end_time": "2019-10-28T22:48:55.146991", "exception": false, "start_time": "2019-10-28T22:48:55.129324", "status": "completed"} tags=[]
building = ef.get_building()  # load example file if no idf filename is provided

parameters = [
    Parameter(
        FieldSelector(
            object_name="Mass NonRes Wall Insulation",  # define wall insulation thickness as a parameter
            field_name="Thickness",
        )
    )
]  # options: `FieldSelector`, `FilterSelecor`, `GenericSelector`

objectives = [
    "Electricity:Facility",
    "Gas:Facility",
]  # these get made into `MeterReader` or `VariableReader`
problem = EPProblem(parameters, objectives)  # problem = parameters + objectives

evaluator = EvaluatorEP(problem, building)  # evaluator = problem + building

# + papermill={"duration": 1.258319, "end_time": "2019-10-28T22:48:56.411178", "exception": false, "start_time": "2019-10-28T22:48:55.152859", "status": "completed"} tags=[]
result = evaluator(
    [0.5]
)  # run the evaluator with wall insulation thickness set to 0.5m
values = dict(zip(objectives, result))
for key, value in values.items():
    print(key, " :: ", "{0:.2f}".format(value / 3.6e6), "kWh")
# -

# ### Different version EnergyPlus

# EvaluatorEP can set the version of EnergyPlus for running simulation by 'version' parameter. See [this notebook](DifferentVersionEP.ipynb) for detail.

evaluator = EvaluatorEP(problem, building)


# + [markdown] papermill={"duration": 0.006409, "end_time": "2019-10-28T22:48:56.424749", "exception": false, "start_time": "2019-10-28T22:48:56.418340", "status": "completed"} tags=[]
# ## EvaluatorGeneric
# The generic evaluator takes a function with the correct number of inputs and produces outputs in the format `(objectives, constraints)` where objectives and constraints are tuples.
# Since this evaluator only uses it's parameters to track the number of inputs and outputs (and their names), we can use numbered placeholders that are automatically generated by `Problem`

# + papermill={"duration": 0.014767, "end_time": "2019-10-28T22:48:56.446148", "exception": false, "start_time": "2019-10-28T22:48:56.431381", "status": "completed"} tags=[]
def function(values):  # define a dummy fucntion f1(x)=x, f2(x)=x^2
    return (values[0], values[0] ** 2)


print("Function value at 4 is:", function([4]))

new_problem = Problem(
    1, 2, 0
)  # this denotes a problem which takes 1 input, produces 2 outputs and has no contraints

# These names are used on the headings of DataFrames generated by the problem and evaluators that use it
print(problem.names(), new_problem.names())

evaluator_1 = EvaluatorGeneric(function, problem)
evaluator_2 = EvaluatorGeneric(function, new_problem)
print(evaluator_1([4]))
print(evaluator_2([4]))

# + papermill={"duration": 0.012825, "end_time": "2019-10-28T22:48:56.476870", "exception": false, "start_time": "2019-10-28T22:48:56.464045", "status": "completed"} tags=[]
evaluator_1([4])

# + [markdown] papermill={"duration": 0.006013, "end_time": "2019-10-28T22:48:56.489250", "exception": false, "start_time": "2019-10-28T22:48:56.483237", "status": "completed"} tags=[]
# ## EvaluatorEH
# The Energy Hub evaluator needs:
# # + an energy hub model
# # + a problem with parameters that can modify it
# # + objectives that correspond to outputs from the model
#
# Parameters are provided as a list of key list mapping lists for the different variables in the model.
# Outputs are provided as a list of the keys from the results of the model.

# + papermill={"duration": 0.027723, "end_time": "2019-10-28T22:48:56.523014", "exception": false, "start_time": "2019-10-28T22:48:56.495291", "status": "completed"} tags=[]
hub = pf.get_hub()  # load the hub model specified in config.yaml

parameters = [
    Parameter(PathSelector(["LOADS", "Elec"])),
    Parameter(PathSelector(["LOADS", "Heat"])),
]  # the parameters are the hourly electricty and heat loads  # the parameters are the hourly electricty and heat loads
objectives = [
    "total_cost",
    "total_carbon",
]  # the objectives are the total_carbon and total_cost variables of the Energy Hub problem
problem = EHProblem(
    parameters, objectives
)  # we make a problem out of the parameters and objectives
evaluatorEH = EvaluatorEH(
    problem, hub
)  # and an Evaluator by combining with the hub model

# + [markdown] papermill={"duration": 0.006858, "end_time": "2019-10-28T22:48:56.537336", "exception": false, "start_time": "2019-10-28T22:48:56.530478", "status": "completed"} tags=[]
# Input values for overwritting the specified parameters can be given in the form of:
# # + single values
# # + a dictionary time series
# # + a dataframe of single values
# # + a dataframe of time series

# + papermill={"duration": 0.016992, "end_time": "2019-10-28T22:48:56.561623", "exception": false, "start_time": "2019-10-28T22:48:56.544631", "status": "completed"} tags=[]
default_timeseries = [
    {
        0: 1.0,
        1: 4.0,
        2: 4.0,
        3: 4.0,
        4: 4.0,
        5: 4.0,
        6: 4.0,
        7: 4.0,
        8: 4.0,
        9: 4.0,
        10: 4.0,
    },
    {
        0: 20.0,
        1: 20.0,
        2: 20.0,
        3: 20.0,
        4: 20.0,
        5: 20.0,
        6: 20.0,
        7: 12.0,
        8: 12.0,
        9: 12.0,
        10: 12.0,
    },
]
just_modified_heat = [
    {
        0: 1.0,
        1: 4.0,
        2: 4.0,
        3: 4.0,
        4: 4.0,
        5: 4.0,
        6: 4.0,
        7: 4.0,
        8: 4.0,
        9: 4.0,
        10: 4.0,
    },
    {
        0: 18.0,
        1: 18.0,
        2: 18.0,
        3: 18.0,
        4: 18.0,
        5: 18.0,
        6: 18.0,
        7: 16.0,
        8: 16.0,
        9: 16.0,
        10: 16.0,
    },
]
just_modified_elec = [
    {
        0: 4.0,
        1: 8.0,
        2: 6.0,
        3: 5.0,
        4: 7.0,
        5: 7.0,
        6: 7.0,
        7: 7.0,
        8: 7.0,
        9: 7.0,
        10: 7.0,
    },
    {
        0: 20.0,
        1: 20.0,
        2: 20.0,
        3: 20.0,
        4: 20.0,
        5: 20.0,
        6: 20.0,
        7: 12.0,
        8: 12.0,
        9: 12.0,
        10: 12.0,
    },
]
modified_timeseries = [
    {
        0: 4.0,
        1: 8.0,
        2: 6.0,
        3: 5.0,
        4: 7.0,
        5: 7.0,
        6: 7.0,
        7: 7.0,
        8: 7.0,
        9: 7.0,
        10: 7.0,
    },
    {
        0: 18.0,
        1: 18.0,
        2: 18.0,
        3: 18.0,
        4: 18.0,
        5: 18.0,
        6: 18.0,
        7: 16.0,
        8: 16.0,
        9: 16.0,
        10: 16.0,
    },
]
timeseries_df = pd.DataFrame(
    np.array(
        [
            default_timeseries,
            just_modified_heat,
            just_modified_elec,
            modified_timeseries,
        ]
    ),
    columns=["p1", "p2"],
)

# + [markdown] papermill={"duration": 0.006246, "end_time": "2019-10-28T22:48:56.574387", "exception": false, "start_time": "2019-10-28T22:48:56.568141", "status": "completed"} tags=[]
# Normally the evaluator can be called directly with the input values, but if using a dataframe as input df_apply must be used.

# + papermill={"duration": 0.351084, "end_time": "2019-10-28T22:48:56.931770", "exception": false, "start_time": "2019-10-28T22:48:56.580686", "status": "completed"} tags=[]
result = evaluatorEH.df_apply(timeseries_df)
result

# + papermill={"duration": 0.007944, "end_time": "2019-10-28T22:48:56.948146", "exception": false, "start_time": "2019-10-28T22:48:56.940202", "status": "completed"} tags=[]
