Skip to content
LIBRARY
Examples.HVAC.HVACClosedLoop.md

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: ​

NameDescriptionUnitsDefault value
NpPrediction horizon [h]–24
Q1Weight on the squared indoor-temperature deviation–40.0
Q2Small regularization weight on the squared compressor power–0.001
TrefIndoor temperature setpoint [°C]–20.0

Variables ​

NameDescriptionUnits
TintIndoor temperature [°C]–
powerCompressor power level applied to the plant [kW]–
electricity_priceElectricity price [SEK/100 per kWh]–

Behavior ​

Behavior of this component cannot be rendered because it includes path variables.

Source ​

dyad
"""
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}
  }
}
end
Flattened Source
dyad
"""
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}
  }
}
end


Test Cases ​

No test cases defined.

  • Examples

  • Experiments

  • Analyses