Building Optimization

This notebook performs building design optimization using EnergyPlus and BESOS helper functions. We load a model from in.idf, define parameters to vary, set objectives, test the model, then run a multi-objective genetic algorithm and plot the optimized designs.

Import libraries

import pandas as pd
from besos import eppy_funcs as ef, sampling
from besos.evaluator import EvaluatorEP
from besos.optimizer import NSGAII, df_solution_to_solutions
from besos.parameters import RangeParameter, expand_plist, wwr
from besos.problem import EPProblem
from matplotlib import pyplot as plt
from platypus import Archive, Hypervolume, Solution

Load the base EnergyPlus .idf file

building = ef.get_building("in.idf")

Define design parameters and ranges

Define a parameter list using a helper function, in this case building orientation and window-to-wall ratio.

parameters = []
parameters = expand_plist(
    {"Building 1": {"North Axis": (0, 359)}}  # Name from IDF Building object
)

parameters.append(
    wwr(RangeParameter(0.1, 0.9))
)  # Add window-to-wall ratio as a parameter between 0.1 and 0.9 using a custom function

Objectives

Using Heating and Cooling energy outputs as simulation objectives, make a problem instance from these parameters and objectives.

objectives = ["DistrictCooling:Facility", "DistrictHeating:Facility"]
besos_problem = EPProblem(parameters, objectives)

Set up EnergyPlus evaluator object to run simulations for this building and problem

evaluator = EvaluatorEP(
    besos_problem, building, out_dir="outputdir", err_dir="outputdir"
)  # outputdir must exist; E+ files will be written there
runs = pd.DataFrame.from_dict(
    {"0": [180, 0.5]}, orient="index"
)  # Make a dataframe of runs with one entry for South and 50% glazing
outputs = evaluator.df_apply(runs)  # Run this as a test
outputs
HBox(children=(FloatProgress(value=0.0, description='Executing', max=1.0, style=ProgressStyle(description_widt…
DistrictCooling:Facility DistrictHeating:Facility
0 3.233564e+09 4.931726e+09

Run the Genetic Algorithm

Run the optimizer using this evaluator for a population size of 20 for 10 generations.

results = NSGAII(evaluator, evaluations=10, population_size=20)
results
North Axis RangeParameter [0.1, 0.9] DistrictCooling:Facility DistrictHeating:Facility violation pareto-optimal
0 263.141621 0.129692 4.295335e+09 2.518508e+09 0 True
1 292.002451 0.227006 4.463966e+09 2.689957e+09 0 False
2 339.185394 0.683410 4.385552e+09 4.164144e+09 0 False
3 299.642312 0.305285 4.484553e+09 2.958204e+09 0 False
4 28.829169 0.280137 4.322465e+09 2.577191e+09 0 False
5 193.942624 0.632927 3.270069e+09 5.663412e+09 0 True
6 284.171269 0.423629 4.453051e+09 3.709380e+09 0 False
7 201.004448 0.205321 3.491288e+09 3.299567e+09 0 True
8 222.356347 0.838719 3.679302e+09 6.640699e+09 0 False
9 300.243976 0.727996 4.598198e+09 4.884347e+09 0 False
10 131.502074 0.272879 3.756134e+09 3.589675e+09 0 False
11 317.780853 0.243146 4.464903e+09 2.490665e+09 0 True
12 317.265378 0.642623 4.529079e+09 4.232315e+09 0 False
13 78.522690 0.536158 4.345231e+09 4.379867e+09 0 False
14 300.109981 0.594342 4.547690e+09 4.280460e+09 0 False
15 233.827874 0.249924 3.905637e+09 3.391033e+09 0 False
16 4.219275 0.607486 4.256356e+09 3.775692e+09 0 False
17 350.548384 0.494473 4.280642e+09 3.334591e+09 0 False
18 60.838750 0.874669 4.558749e+09 5.692981e+09 0 False
19 311.211108 0.498106 4.510503e+09 3.688458e+09 0 False

Visualize the results

optres = results.loc[
    results["pareto-optimal"] == True, :
]  # Get only the optimal results
plt.plot(
    results["DistrictCooling:Facility"], results["DistrictHeating:Facility"], "x"
)  # Plot all results in the background as blue crosses
plt.plot(
    optres["DistrictCooling:Facility"], optres["DistrictHeating:Facility"], "ro"
)  # Plot optimal results in red
plt.xlabel("Cooling demand")
plt.ylabel("Heating demand")
Text(0, 0.5, 'Heating demand')
../../_images/BuildingOptimization_15_1.png
optres = optres.sort_values("DistrictCooling:Facility")  # Sort by the first objective
optresplot = optres.drop(columns="violation")  # Remove the constraint violation column
ax = optresplot.plot.bar(
    subplots=True, legend=None, figsize=(10, 10)
)  # Plot the variable values of each of the optimal solutions
/usr/local/lib/python3.7/dist-packages/pandas/plotting/_matplotlib/tools.py:307: MatplotlibDeprecationWarning:
The rowNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().rowspan.start instead.
  layout[ax.rowNum, ax.colNum] = ax.get_visible()
/usr/local/lib/python3.7/dist-packages/pandas/plotting/_matplotlib/tools.py:307: MatplotlibDeprecationWarning:
The colNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().colspan.start instead.
  layout[ax.rowNum, ax.colNum] = ax.get_visible()
/usr/local/lib/python3.7/dist-packages/pandas/plotting/_matplotlib/tools.py:313: MatplotlibDeprecationWarning:
The rowNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().rowspan.start instead.
  if not layout[ax.rowNum + 1, ax.colNum]:
/usr/local/lib/python3.7/dist-packages/pandas/plotting/_matplotlib/tools.py:313: MatplotlibDeprecationWarning:
The colNum attribute was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use ax.get_subplotspec().colspan.start instead.
  if not layout[ax.rowNum + 1, ax.colNum]:
../../_images/BuildingOptimization_16_1.png

Hypervolume

Now that initial results have been produced and verified against expectations, enlarge evaluations and population size to produce increased optimization of results.

results_2 = NSGAII(evaluator, evaluations=20, population_size=50)

Compare first run and second run

optres_2 = results_2.loc[results_2["pareto-optimal"] == True, :]
plt.plot(
    optres["DistrictCooling:Facility"], optres["DistrictHeating:Facility"], "bo"
)  # Plot first optimal results in blue
plt.plot(
    optres_2["DistrictCooling:Facility"], optres_2["DistrictHeating:Facility"], "ro"
)  # Plot second optimal results in red
plt.xlabel("Cooling demand")
plt.ylabel("Heating demand")
Text(0, 0.5, 'Heating demand')
../../_images/BuildingOptimization_21_1.png

Calculate the hypervolume

reference_set = Archive()
platypus_problem = evaluator.to_platypus()
for _ in range(20):
    solution = Solution(platypus_problem)
    solution.variables = sampling.dist_sampler(
        sampling.lhs, besos_problem, 1
    ).values.tolist()[0]
    solution.evaluate()
    reference_set.add(solution)

hyp = Hypervolume(reference_set)
print(
    "Hypervolume for result 1:",
    hyp.calculate(df_solution_to_solutions(results, platypus_problem, besos_problem)),
)
print(
    "Hypervolume for result 2:",
    hyp.calculate(df_solution_to_solutions(results_2, platypus_problem, besos_problem)),
)
Hypervolume for result 1: 0.27192093845955767
Hypervolume for result 2: 0.23978050252228306