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 RangeDescriptor, 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(RangeDescriptor(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 RangeDescriptor [0.1, 0.9] DistrictCooling:Facility DistrictHeating:Facility violation pareto-optimal
0 37.466568 0.779693 4.460085e+09 4.860635e+09 0 False
1 243.990689 0.467029 4.029894e+09 4.453658e+09 0 False
2 224.662175 0.685257 3.714934e+09 5.805083e+09 0 False
3 233.059795 0.400315 3.865349e+09 4.204440e+09 0 True
4 215.886962 0.681890 3.564599e+09 5.842255e+09 0 False
5 279.921039 0.867322 4.575077e+09 5.872849e+09 0 False
6 313.596088 0.405313 4.486791e+09 3.253404e+09 0 False
7 348.248353 0.101710 4.358378e+09 1.701099e+09 0 True
8 132.035405 0.467023 3.716682e+09 4.654626e+09 0 True
9 269.248373 0.530728 4.365569e+09 4.459456e+09 0 False
10 350.161374 0.347721 4.284927e+09 2.703139e+09 0 False
11 234.224202 0.602762 3.879037e+09 5.255264e+09 0 False
12 89.948274 0.145898 4.234634e+09 2.592709e+09 0 True
13 180.197818 0.541119 3.221832e+09 5.150586e+09 0 True
14 294.747583 0.414158 4.490630e+09 3.517963e+09 0 False
15 318.608866 0.467363 4.479668e+09 3.453551e+09 0 False
16 350.864950 0.762852 4.326616e+09 4.475655e+09 0 False
17 186.068791 0.562011 3.231265e+09 5.262056e+09 0 False
18 13.197950 0.758258 4.309214e+09 4.462021e+09 0 False
19 46.755428 0.818880 4.519118e+09 5.201453e+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.5044186053393032
Hypervolume for result 2: 0.6217620026588312