Examples.HVAC.HVACClosedLoop ​
Economic MPC of the tutorial house: HVACPlant under a LinearMPCController that minimizes Q1 (Tint - Tref)^2 + price(t) * power over a 24 h horizon with the hourly electricity price and outdoor temperature forecasts (HVACForecast) as previewed generalized parameter and measured disturbance, the power level bounded to 0–4 kW, and a StateDisturbance offset-free observer estimating the plant state from the sampled indoor temperature. The compressor level is continuous here (the tutorial restricts it to integers); a terminal weight 10 Q1 on the indoor temperature and a small Q2 regularization complete the quadratic cost.
The controller's prediction model is the plant itself (dynamics = plant), with the compressor power as the manipulated input, the outdoor temperature as the measured disturbance and the indoor temperature as the output; the controller linearizes and discretizes it. Analysis points u_ap, d_ap and y_ap sit on the plant's inputs and output.
Usage ​
MPCComponents.Examples.HVAC.HVACClosedLoop(Tref=20.0)
Parameters: ​
| Name | Description | Units | Default value |
|---|---|---|---|
Np | Prediction horizon [h] | – | 24 |
Q1 | Weight on the squared indoor-temperature deviation | – | 40.0 |
Q2 | Small regularization weight on the squared compressor power | – | 0.001 |
Tref | Indoor temperature setpoint [°C] | – | 20.0 |
Variables ​
| Name | Description | Units |
|---|---|---|
Tint | Indoor temperature [°C] | – |
power | Compressor power level applied to the plant [kW] | – |
electricity_price | Electricity price [SEK/100 per kWh] | – |
Behavior ​
Behavior of this component cannot be rendered because it includes path variables.
Source ​
"""
Economic MPC of the tutorial house: `HVACPlant` under a `LinearMPCController` that minimizes
`Q1 (Tint - Tref)^2 + price(t) * power` over a 24 h horizon with the hourly electricity price and outdoor
temperature forecasts (`HVACForecast`) as previewed generalized parameter and measured disturbance, the
power level bounded to 0–4 kW, and a `StateDisturbance` offset-free observer estimating the plant state
from the sampled indoor temperature. The compressor level is continuous here (the tutorial restricts it
to integers); a terminal weight `10 Q1` on the indoor temperature and a small `Q2` regularization complete
the quadratic cost.
The controller's prediction model is the plant itself (`dynamics = plant`), with the compressor power as
the manipulated input, the outdoor temperature as the measured disturbance and the indoor temperature
as the output; the controller linearizes and discretizes it. Analysis points `u_ap`, `d_ap` and `y_ap`
sit on the plant's inputs and output.
"""
example component HVACClosedLoop
plant = HVACPlant() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 700, "y1": 400, "x2": 800, "y2": 500, "rot": 0}
},
"tags": []
}
}
weather = HVACWeather() {
"Dyad": {"placement": {"diagram": {"x1": 500, "y1": 520, "x2": 600, "y2": 620}}}
}
clock = DiscreteComponents.PeriodicClock(dt = 1.0) {
"Dyad": {"placement": {"diagram": {"x1": 470, "y1": 820, "x2": 530, "y2": 880}}}
}
reference = BlockComponents.Sources.Constant(k = Tref) {
"Dyad": {"placement": {"diagram": {"x1": 20, "y1": 240, "x2": 120, "y2": 340}}}
}
"Samples the setpoint"
sample_r = DiscreteComponents.Sampler() {
"Dyad": {"placement": {"diagram": {"x1": 150, "y1": 240, "x2": 250, "y2": 340}}}
}
"Electricity price forecast over the horizon"
price = HVACForecast(signal = HVACSignal.ElectricityPrice(), final Np = Np) {
"Dyad": {"placement": {"diagram": {"x1": 150, "y1": 370, "x2": 250, "y2": 470}}}
}
"Outdoor temperature forecast over the horizon"
forecast = HVACForecast(signal = HVACSignal.OutdoorTemperature(), final Np = Np) {
"Dyad": {"placement": {"diagram": {"x1": 150, "y1": 510, "x2": 250, "y2": 610}}}
}
mpc = MPCComponents.LinearMPCController(dynamics = plant, inputs = ["power"], known_disturbance_inputs = ["Text"], outputs = ["y"], Ts = 1.0, final Np = Np, final Q1 = [Q1], final Q2 = [Q2], final Qf = [10.0 * Q1], Eu = [[1.0]], umin = [0.0], umax = [4.0], disturbance_preview = true, parameter_preview = true, offset_free = MPCComponents.OffsetFreeMethod.StateDisturbance()) {
"Dyad": {"placement": {"diagram": {"x1": 340, "y1": 390, "x2": 440, "y2": 490}}}
}
hold = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 500, "y1": 390, "x2": 600, "y2": 490}}}
}
samplewithadeffects = DiscreteComponents.SampleWithADEffects(y_min = -30, y_max = 50, bits = 12) {
"Dyad": {"placement": {"diagram": {"x1": 600, "x2": 500, "y1": 690, "y2": 790}}}
}
"Prediction horizon [h]"
structural parameter Np::Integer = 24
"Weight on the squared indoor-temperature deviation"
structural parameter Q1::Real = 40.0
"Small regularization weight on the squared compressor power"
structural parameter Q2::Real = 0.001
"Indoor temperature setpoint [°C]"
parameter Tref::Real = 20.0
"Indoor temperature [°C]"
variable Tint::Real
"Compressor power level applied to the plant [kW]"
variable power::Real
"Electricity price [SEK/100 per kWh]"
variable electricity_price::Real
relations
Tint = plant.y
power = hold.y
electricity_price = hvac_electricity_price(time)
connect(weather.y, plant.Text) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 650, "y": 570}, {"x": 650, "y": 460}], "E": 2}],
"path": {}
}
}
d_ap: analysis_point(weather.y, plant.Text)
connect(hold.y, plant.power) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
u_ap: analysis_point(hold.y, plant.power)
y_ap: analysis_point(plant.y, samplewithadeffects.u)
connect(clock.y, mpc.y[1], samplewithadeffects.y) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 400, "y": 850}], "E": -1},
{"S": -1, "M": [{"x": 310, "y": 740}, {"x": 310, "y": 474}], "E": 2},
{"S": 3, "M": [], "E": -1}
],
"junctions": [{"x": 400, "y": 740}],
"path": {}
}
}
connect(reference.y, sample_r.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(sample_r.y, mpc.r[1]) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 290, "y": 290}, {"x": 290, "y": 402}], "E": 2}],
"path": {}
}
}
connect(forecast.y, mpc.d) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 290, "y": 560}, {"x": 290, "y": 453}], "E": 2}],
"path": {}
}
}
connect(price.y, mpc.p) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 280, "y": 420}, {"x": 280, "y": 425}], "E": 2}],
"path": {}
}
}
connect(mpc.u[1], hold.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 480, "y": 428}, {"x": 480, "y": 440}], "E": 2}],
"path": {}
}
}
connect(samplewithadeffects.u, plant.y) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 840, "y": 740}, {"x": 840, "y": 450}], "E": 2}],
"path": {}
}
}
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/HVACClosedLoop.svg"},
"doc": {"behavior": false}
}
}
endFlattened Source
"""
Economic MPC of the tutorial house: `HVACPlant` under a `LinearMPCController` that minimizes
`Q1 (Tint - Tref)^2 + price(t) * power` over a 24 h horizon with the hourly electricity price and outdoor
temperature forecasts (`HVACForecast`) as previewed generalized parameter and measured disturbance, the
power level bounded to 0–4 kW, and a `StateDisturbance` offset-free observer estimating the plant state
from the sampled indoor temperature. The compressor level is continuous here (the tutorial restricts it
to integers); a terminal weight `10 Q1` on the indoor temperature and a small `Q2` regularization complete
the quadratic cost.
The controller's prediction model is the plant itself (`dynamics = plant`), with the compressor power as
the manipulated input, the outdoor temperature as the measured disturbance and the indoor temperature
as the output; the controller linearizes and discretizes it. Analysis points `u_ap`, `d_ap` and `y_ap`
sit on the plant's inputs and output.
"""
example component HVACClosedLoop
plant = HVACPlant() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 700, "y1": 400, "x2": 800, "y2": 500, "rot": 0}
},
"tags": []
}
}
weather = HVACWeather() {
"Dyad": {"placement": {"diagram": {"x1": 500, "y1": 520, "x2": 600, "y2": 620}}}
}
clock = DiscreteComponents.PeriodicClock(dt = 1.0) {
"Dyad": {"placement": {"diagram": {"x1": 470, "y1": 820, "x2": 530, "y2": 880}}}
}
reference = BlockComponents.Sources.Constant(k = Tref) {
"Dyad": {"placement": {"diagram": {"x1": 20, "y1": 240, "x2": 120, "y2": 340}}}
}
"Samples the setpoint"
sample_r = DiscreteComponents.Sampler() {
"Dyad": {"placement": {"diagram": {"x1": 150, "y1": 240, "x2": 250, "y2": 340}}}
}
"Electricity price forecast over the horizon"
price = HVACForecast(signal = HVACSignal.ElectricityPrice(), final Np = Np) {
"Dyad": {"placement": {"diagram": {"x1": 150, "y1": 370, "x2": 250, "y2": 470}}}
}
"Outdoor temperature forecast over the horizon"
forecast = HVACForecast(signal = HVACSignal.OutdoorTemperature(), final Np = Np) {
"Dyad": {"placement": {"diagram": {"x1": 150, "y1": 510, "x2": 250, "y2": 610}}}
}
mpc = MPCComponents.LinearMPCController(dynamics = plant, inputs = ["power"], known_disturbance_inputs = ["Text"], outputs = ["y"], Ts = 1.0, final Np = Np, final Q1 = [Q1], final Q2 = [Q2], final Qf = [10.0 * Q1], Eu = [[1.0]], umin = [0.0], umax = [4.0], disturbance_preview = true, parameter_preview = true, offset_free = MPCComponents.OffsetFreeMethod.StateDisturbance()) {
"Dyad": {"placement": {"diagram": {"x1": 340, "y1": 390, "x2": 440, "y2": 490}}}
}
hold = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 500, "y1": 390, "x2": 600, "y2": 490}}}
}
samplewithadeffects = DiscreteComponents.SampleWithADEffects(y_min = -30, y_max = 50, bits = 12) {
"Dyad": {"placement": {"diagram": {"x1": 600, "x2": 500, "y1": 690, "y2": 790}}}
}
"Prediction horizon [h]"
structural parameter Np::Integer = 24
"Weight on the squared indoor-temperature deviation"
structural parameter Q1::Real = 40.0
"Small regularization weight on the squared compressor power"
structural parameter Q2::Real = 0.001
"Indoor temperature setpoint [°C]"
parameter Tref::Real = 20.0
"Indoor temperature [°C]"
variable Tint::Real
"Compressor power level applied to the plant [kW]"
variable power::Real
"Electricity price [SEK/100 per kWh]"
variable electricity_price::Real
relations
Tint = plant.y
power = hold.y
electricity_price = hvac_electricity_price(time)
connect(weather.y, plant.Text) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 650, "y": 570}, {"x": 650, "y": 460}], "E": 2}],
"path": {}
}
}
d_ap: analysis_point(weather.y, plant.Text)
connect(hold.y, plant.power) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
u_ap: analysis_point(hold.y, plant.power)
y_ap: analysis_point(plant.y, samplewithadeffects.u)
connect(clock.y, mpc.y[1], samplewithadeffects.y) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 400, "y": 850}], "E": -1},
{"S": -1, "M": [{"x": 310, "y": 740}, {"x": 310, "y": 474}], "E": 2},
{"S": 3, "M": [], "E": -1}
],
"junctions": [{"x": 400, "y": 740}],
"path": {}
}
}
connect(reference.y, sample_r.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(sample_r.y, mpc.r[1]) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 290, "y": 290}, {"x": 290, "y": 402}], "E": 2}],
"path": {}
}
}
connect(forecast.y, mpc.d) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 290, "y": 560}, {"x": 290, "y": 453}], "E": 2}],
"path": {}
}
}
connect(price.y, mpc.p) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 280, "y": 420}, {"x": 280, "y": 425}], "E": 2}],
"path": {}
}
}
connect(mpc.u[1], hold.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 480, "y": 428}, {"x": 480, "y": 440}], "E": 2}],
"path": {}
}
}
connect(samplewithadeffects.u, plant.y) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 840, "y": 740}, {"x": 840, "y": 450}], "E": 2}],
"path": {}
}
}
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/HVACClosedLoop.svg"},
"doc": {"behavior": false}
}
}
endTest Cases ​
No test cases defined.
Related ​
Examples
Experiments
Analyses