Skip to content

Commit

Permalink
Merge branch 'release/0.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
stroitzsch committed Nov 13, 2019
2 parents e097ad9 + bde7f7f commit f65ee6b
Show file tree
Hide file tree
Showing 22 changed files with 1,566 additions and 677 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# CoBMo - Control-oriented Building Model

The Control-oriented Building Model (CoBMo) is a building modelling framework catering specifically for the formulation of MPC problems for thermal building systems. CoBMo provides a mathematical model which expresses the relationship between the electric load of the thermal building systems and the indoor air climate with consideration for interactions of the building with its environment, its occupants and appliances. To this end, CoBMo currently implements models for 1) the thermal comfort of building occupants as well as 2) the indoor air quality.
[![DOI](https://zenodo.org/badge/173782015.svg)](https://zenodo.org/badge/latestdoi/173782015)

The Control-oriented Building Model (CoBMo) is a building modelling framework catering specifically for the formulation of MPC problems for thermal building systems by keeping all model equations in the linear, i.e., convex, domain. CoBMo provides a mathematical model which expresses the relationship between the electric load of the thermal building systems and the indoor air climate with consideration for interactions of the building with its environment, its occupants and appliances. To this end, CoBMo currently implements models for 1) the thermal comfort of building occupants as well as 2) the indoor air quality.

## Documentation

Expand All @@ -9,7 +11,7 @@ The preliminary CoBMo documentation is located at: [cobmo.readthedocs.io](https:
## Installation

1. Check requirements:
- Python 3.6
- Python 3.7
- [Gurobi Optimizer](http://www.gurobi.com/)
2. Clone or download repository.
3. In your Python environment, run `pip install -e path_to_cobmo_repository`.
Expand Down
57 changes: 41 additions & 16 deletions cobmo/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,16 @@ class Building(object):
"""

def __init__(self, conn, scenario_name):
"""Initialize building model for given `scenario_name`."""

# Obtain scenario name.
self.scenario_name = scenario_name
"""Initialize building model for given `scenario_name`."""

# Instantiate model variables.
self.disturbance_timeseries = pd.DataFrame()
self.electricity_price_timeseries = pd.DataFrame()

# Load building model definition.
# TODO: Wrap into dedicated function and resample based on timesteps.
self.electricity_prices = (
pd.read_sql(
"""
SELECT * FROM electricity_price_timeseries
WHERE price_type=(
SELECT price_type from building_scenarios
WHERE scenario_name='{}'
)
""".format(scenario_name),
conn
)
)
self.electricity_prices.index = pd.to_datetime(self.electricity_prices['time'])
self.building_scenarios = (
pd.read_sql(
"""
Expand All @@ -42,7 +33,7 @@ def __init__(self, conn, scenario_name):
JOIN building_linearization_types USING (linearization_type)
LEFT JOIN building_storage_types USING (building_storage_type)
WHERE scenario_name='{}'
""".format(scenario_name),
""".format(self.scenario_name),
conn
)
)
Expand Down Expand Up @@ -547,6 +538,7 @@ def __init__(self, conn, scenario_name):

# Define timeseries.
self.load_disturbance_timeseries(conn)
self.load_electricity_price_timeseries(conn)
self.define_output_constraint_timeseries(conn)

# Convert to time discrete model.
Expand Down Expand Up @@ -3640,6 +3632,39 @@ def load_disturbance_timeseries(self, conn):
axis='columns',
).rename_axis('disturbance_name', axis='columns')

def load_electricity_price_timeseries(self, conn):
if pd.isnull(self.building_scenarios['price_type'][0]):
# If no price_type defined, generate a flat price signal.
self.electricity_price_timeseries = (
pd.DataFrame(
[[None, 1.0]],
columns=['price_type', 'price'],
index=self.set_timesteps
)
)
else:
self.electricity_price_timeseries = (
pd.read_sql(
"""
SELECT * FROM electricity_price_timeseries
WHERE price_type=(
SELECT price_type from building_scenarios
WHERE scenario_name='{}'
)
""".format(self.scenario_name),
conn,
index_col='time',
parse_dates=['time']
)
)

# Reindex / interpolate to match set_timesteps.
self.electricity_price_timeseries.reindex(
self.set_timesteps
).interpolate(
'quadratic'
)

def define_output_constraint_timeseries(self, conn):
"""
- Generate minimum/maximum constraint timeseries based on `building_zone_constraint_profiles`.
Expand Down
2 changes: 1 addition & 1 deletion cobmo/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

# Optimization solver settings.
solver_name = 'gurobi' # Must be valid input string for Pyomo's `SolverFactory`.
solver_output = True # If True, activate verbose solver output.
solver_output = False # If True, activate verbose solver output.

# Logger settings.
logging_level = logging.DEBUG
Expand Down
66 changes: 63 additions & 3 deletions cobmo/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ def __init__(
self,
conn,
building,
problem_type='operation' # Choices: 'operation', 'storage_planning', 'storage_planning_baseline'
problem_type='operation',
# Choices: 'operation', 'storage_planning', 'storage_planning_baseline', 'load_reduction',
# 'price_sensitivity'
output_timeseries_reference=None,
load_reduction_start_time=None,
load_reduction_end_time=None,
price_sensitivity_factor=None,
price_sensitivity_timestep=None,
):
"""Initialize controller object based on given `building` object.
Expand All @@ -23,6 +30,15 @@ def __init__(
time_start = time.clock()
self.building = building
self.problem_type = problem_type
self.output_timeseries_reference = output_timeseries_reference
self.load_reduction_start_time = load_reduction_start_time
self.load_reduction_end_time = load_reduction_end_time
self.price_sensitivity_factor = price_sensitivity_factor
self.price_sensitivity_timestep = price_sensitivity_timestep

# Copy `electricity_price_timeseries` to allow local modifications.
self.electricity_price_timeseries = self.building.electricity_price_timeseries.copy()

self.problem = pyo.ConcreteModel()
self.solver = pyo.SolverFactory(cobmo.config.solver_name)
self.result = None
Expand Down Expand Up @@ -60,6 +76,11 @@ def __init__(
if self.problem_type == 'storage_planning_baseline':
# Force storage size to zero for baseline case.
self.problem.variable_storage_size = [0.0]
if self.problem_type == 'load_reduction':
self.problem.variable_load_reduction = pyo.Var(
[0],
domain=pyo.NonNegativeReals
)

# Define constraints.
self.problem.constraints = pyo.ConstraintList()
Expand Down Expand Up @@ -183,6 +204,31 @@ def __init__(
* 1.0e100 # Large constant as replacement for infinity.
)

# Demand side flexibility auxiliary constraints.
elif self.problem_type == 'load_reduction':
for timestep in self.building.set_timesteps:
if (
(timestep >= self.load_reduction_start_time)
and (timestep < self.load_reduction_end_time)
):
# TODO: Introduce total electric demand in building outputs.
self.problem.constraints.add(
sum(
self.problem.variable_output_timeseries[timestep, output]
if (('electric_power' in output) and not ('storage_to_zone' in output)) else 0.0
for output in self.building.set_outputs
)
==
(
(1.0 - (self.problem.variable_load_reduction[0] / 100.0))
* sum(
self.output_timeseries_reference.loc[timestep, output]
if (('electric_power' in output) and not ('storage_to_zone' in output)) else 0.0
for output in self.building.set_outputs
)
)
)

# Define components of the objective.
self.operation_cost = 0.0
self.investment_cost = 0.0
Expand All @@ -196,18 +242,29 @@ def __init__(
* self.building.building_scenarios['storage_lifetime'][0] # Storage lifetime in years.
* 14.0 # 14 levels at CREATE Tower. # TODO: Check if considered properly in storage size.
)
elif self.problem_type == 'load_reduction':
# Adjust weight of operation cost when running load reduction problem.
# - Workaround for unrealistic demand when not considering operation cost at all.
# - This is a tuning parameter (has impact on load reduction result).
self.operation_cost_factor = 1.0e-6
else:
# No scaling needed if not running planning problem.
self.operation_cost_factor = 1.0

# Modify price for price sensitivity evaluation.
if self.problem_type == 'price_sensitivity':
self.electricity_price_timeseries.at[self.price_sensitivity_timestep, 'price'] *= (
self.price_sensitivity_factor
)

# Operation cost (OPEX).
for timestep in self.building.set_timesteps:
for output in self.building.set_outputs:
if ('electric_power' in output) and not ('storage_to_zone' in output):
self.operation_cost += (
self.problem.variable_output_timeseries[timestep, output]
* timestep_delta.seconds / 3600.0 / 1000.0 # Ws in kWh.
* self.building.electricity_prices.loc[timestep, 'price']
* timestep_delta.seconds / 3600.0 / 1000.0 # W in kWh.
* self.electricity_price_timeseries.loc[timestep, 'price']
* self.operation_cost_factor
)

Expand All @@ -233,6 +290,9 @@ def __init__(
+ self.problem.variable_storage_exists[0] # No unit.
* self.building.building_scenarios['storage_planning_fixed_installation_cost'][0] # In SGD.
)
elif self.problem_type == 'load_reduction':
# TODO: Introduce dedicated cost for demand side flexibility indicators.
self.investment_cost -= self.problem.variable_load_reduction[0] # In percent.

# Define objective.
self.problem.objective = pyo.Objective(
Expand Down
Loading

0 comments on commit f65ee6b

Please sign in to comment.