GainScheduledPitchControl Icon

Examples.GainScheduledPitchControl

Gain-scheduled pitch-attitude control of an aircraft during a climb with decreasing airspeed.

The aircraft climbs from sea level while the airspeed decreases, so the dynamic pressure, and with it the gain and the natural frequency of the pitch dynamics in AircraftPitch, decreases by more than an order of magnitude over the simulation. The altitude and the airspeed are sampled and passed to PitchGainSchedule, which computes the gains of the DiscretePIDScheduled controller from the two scheduling variables by pole placement, such that the closed-loop poles are the same at every operating point. The set-point weights wp = wd = 0 let the reference enter through the integral part only, so the response to a reference step is determined by the closed-loop poles alone. The controller tracks a square-wave pitch reference, so the step response of the closed loop is exercised at several operating points along the trajectory. The scheduling logic is contained entirely in PitchGainSchedule; the controller itself only receives the gains as input signals.

Usage

DiscreteComponents.Examples.GainScheduledPitchControl(rho0=1.225, H=8500, c_u=6e-4, c_theta=4e-4, c_omega=7.4e-3, u_max=0.4)

Parameters:

NameDescriptionUnitsDefault value
rho0Air density at sea level [kg/m³]–1.225
HScale height of the exponential atmosphere [m]–8500
c_uPitch acceleration per unit elevator deflection and unit dynamic pressure–6e-4
c_thetaPitch acceleration per unit pitch angle and unit dynamic pressure–4e-4
c_omegaPitch damping coefficient–7.4e-3
u_maxMaximum elevator deflection [rad]–0.4

Behavior

42-element Vector{Symbolics.Equation}:
 connect(reference.y(t), sampler_r.u(t))
 connect(sampler_r.y(t), pid.u_s(t), periodicclock.y(t))
 connect(altitude.y(t), sampler_h.u(t), aircraft.h(t))
 connect(airspeed.y(t), sampler_V.u(t), aircraft.V(t))
 connect(sampler_h.y(t), schedule.h(t))
 connect(sampler_V.y(t), schedule.V(t))
 connect(schedule.K(t), pid.K(t))
 connect(schedule.Ti(t), pid.Ti(t))
 connect(schedule.Td(t), pid.Td(t))
 connect(pid.y(t), zeroorderhold.u(t))
 ⋮
 pid₊I(t) ~ Shift(t, -1)(pid₊I(t)) + ModelingToolkitBase.SampleTime(ModelingToolkitBase.ShiftIndex(InferredClock.Inferred(Base.UUID("6bcf6967-5e85-4454-968a-3f4892df463c")), 0), nothing)()*Shift(t, -1)(pid₊eI(t))
 pid₊wde(t) ~ -pid₊u_m(t) + pid₊u_s(t)*pid₊wd
 pid₊D(t) ~ (Shift(t, -1)(pid₊D(t))*pid₊Td(t)) / (pid₊Td(t) + ModelingToolkitBase.SampleTime(ModelingToolkitBase.ShiftIndex(InferredClock.Inferred(Base.UUID("6bcf6967-5e85-4454-968a-3f4892df463c")), 0), nothing)()*pid₊Nd) + ((-Shift(t, -1)(pid₊wde(t)) + pid₊wde(t))*pid₊K(t)*pid₊Nd*pid₊Td(t)) / (pid₊Td(t) + ModelingToolkitBase.SampleTime(ModelingToolkitBase.ShiftIndex(InferredClock.Inferred(Base.UUID("6bcf6967-5e85-4454-968a-3f4892df463c")), 0), nothing)()*pid₊Nd)
 pid₊v(t) ~ pid₊D(t) + pid₊I(t) + pid₊uff(t) + pid₊K(t)*(-pid₊u_m(t) + pid₊u_s(t)*pid₊wp)
 zeroorderhold₊y(t) ~ ModelingToolkitBase.Hold()(zeroorderhold₊u(t))
 aircraft₊q(t) ~ 0.5(aircraft₊V(t)^2)*aircraft₊rho0*exp((-aircraft₊h(t)) / aircraft₊H)
 Differential(t, 1)(aircraft₊pitch(t)) ~ aircraft₊omega(t)
 Differential(t, 1)(aircraft₊omega(t)) ~ (-aircraft₊c_omega*aircraft₊omega(t)*aircraft₊q(t)) / aircraft₊V(t) + (-aircraft₊c_theta*aircraft₊pitch(t) + aircraft₊c_u*aircraft₊u(t))*aircraft₊q(t)
 aircraft₊theta(t) ~ aircraft₊pitch(t)

Source

"""
Gain-scheduled pitch-attitude control of an aircraft during a climb with decreasing airspeed.

The aircraft climbs from sea level while the airspeed decreases, so the dynamic pressure, and with it the gain and the natural frequency of the pitch dynamics in `AircraftPitch`, decreases by more than an order of magnitude over the simulation. The altitude and the airspeed are sampled and passed to `PitchGainSchedule`, which computes the gains of the `DiscretePIDScheduled` controller from the two scheduling variables by pole placement, such that the closed-loop poles are the same at every operating point. The set-point weights `wp = wd = 0` let the reference enter through the integral part only, so the response to a reference step is determined by the closed-loop poles alone. The controller tracks a square-wave pitch reference, so the step response of the closed loop is exercised at several operating points along the trajectory. The scheduling logic is contained entirely in `PitchGainSchedule`; the controller itself only receives the gains as input signals.
"""
component GainScheduledPitchControl
  "Air density at sea level [kg/m³]"
  parameter rho0::Real = 1.225
  "Scale height of the exponential atmosphere [m]"
  parameter H::Real = 8500
  "Pitch acceleration per unit elevator deflection and unit dynamic pressure"
  parameter c_u::Real = 6e-4
  "Pitch acceleration per unit pitch angle and unit dynamic pressure"
  parameter c_theta::Real = 4e-4
  "Pitch damping coefficient"
  parameter c_omega::Real = 7.4e-3
  "Maximum elevator deflection [rad]"
  parameter u_max::Real = 0.4
  reference = DiscreteComponents.Examples.PitchReference(amplitude = 0.1, period = 20, start_time = 1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 0, "y1": 450, "x2": 100, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
  altitude = BlockComponents.Sources.Ramp(offset = 0, height = 12000, duration = 100, start_time = 5) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 0, "y1": 50, "x2": 100, "y2": 150, "rot": 0}
      },
      "tags": []
    }
  }
  airspeed = BlockComponents.Sources.Ramp(offset = 250, height = -140, duration = 100, start_time = 5) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 0, "y1": 250, "x2": 100, "y2": 350, "rot": 0}
      },
      "tags": []
    }
  }
  periodicclock = DiscreteComponents.PeriodicClock(dt = 0.02) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 200, "y1": 650, "x2": 300, "y2": 750, "rot": 0}
      },
      "tags": []
    }
  }
  sampler_r = DiscreteComponents.Sampler() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 200, "y1": 450, "x2": 300, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
  sampler_h = DiscreteComponents.Sampler() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 200, "y1": 50, "x2": 300, "y2": 150, "rot": 0}
      },
      "tags": []
    }
  }
  sampler_V = DiscreteComponents.Sampler() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 200, "y1": 250, "x2": 300, "y2": 350, "rot": 0}
      },
      "tags": []
    }
  }
  sampler_theta = DiscreteComponents.Sampler() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 700, "y1": 650, "x2": 600, "y2": 750, "rot": 0}
      },
      "tags": []
    }
  }
  schedule = DiscreteComponents.Examples.PitchGainSchedule(final rho0 = rho0, final H = H, final c_u = c_u, final c_theta = c_theta, final c_omega = c_omega, omega = 4, zeta = 0.7, alpha = 1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 400, "y1": 150, "x2": 500, "y2": 250, "rot": 0}
      },
      "tags": []
    }
  }
  pid = DiscreteComponents.DiscretePIDScheduled(with_I = true, with_D = true, final y_max = u_max, final y_min = -u_max, Nd = 10, wp = 0, wd = 0) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 400, "y1": 450, "x2": 500, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
  zeroorderhold = DiscreteComponents.ZeroOrderHold() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 600, "y1": 450, "x2": 700, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
  aircraft = DiscreteComponents.Examples.AircraftPitch(final rho0 = rho0, final H = H, final c_u = c_u, final c_theta = c_theta, final c_omega = c_omega) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 800, "y1": 450, "x2": 900, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
relations
  connect(reference.y, sampler_r.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(sampler_r.y, pid.u_s, periodicclock.y) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [{"x": 350, "y": 472}], "E": 2},
        {
          "S": 3,
          "M": [{"x": 180, "y": 700}, {"x": 180, "y": 620}, {"x": 350, "y": 620}],
          "E": -1
        }
      ],
      "junctions": [{"x": 350, "y": 500}],
      "renderStyle": "standard"
    }
  }
  connect(altitude.y, sampler_h.u, aircraft.h) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [], "E": 2},
        {"S": -1, "M": [{"x": 150, "y": 20}, {"x": 825, "y": 20}], "E": 3}
      ],
      "junctions": [{"x": 150, "y": 100}],
      "renderStyle": "standard"
    }
  }
  connect(airspeed.y, sampler_V.u, aircraft.V) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [], "E": 2},
        {"S": -1, "M": [{"x": 150, "y": 390}, {"x": 875, "y": 390}], "E": 3}
      ],
      "junctions": [{"x": 150, "y": 300}],
      "renderStyle": "standard"
    }
  }
  connect(sampler_h.y, schedule.h) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 350, "y": 100}, {"x": 350, "y": 175}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(sampler_V.y, schedule.V) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 350, "y": 300}, {"x": 350, "y": 225}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(schedule.K, pid.K) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(schedule.Ti, pid.Ti) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(schedule.Td, pid.Td) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(pid.y, zeroorderhold.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(zeroorderhold.y, aircraft.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(aircraft.theta, sampler_theta.u) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 950, "y": 500}, {"x": 950, "y": 700}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(sampler_theta.y, pid.u_m) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 380, "y": 700}, {"x": 380, "y": 524}], "E": 2}],
      "renderStyle": "standard"
    }
  }
metadata {
  "Dyad": {
    "experiments": {},
    "tests": {},
    "labels": [],
    "icons": {"default": "dyad://Dyad/Default.svg"},
    "path": {},
    "doc": {"behavior": true}
  }
}
end
Flattened Source
"""
Gain-scheduled pitch-attitude control of an aircraft during a climb with decreasing airspeed.

The aircraft climbs from sea level while the airspeed decreases, so the dynamic pressure, and with it the gain and the natural frequency of the pitch dynamics in `AircraftPitch`, decreases by more than an order of magnitude over the simulation. The altitude and the airspeed are sampled and passed to `PitchGainSchedule`, which computes the gains of the `DiscretePIDScheduled` controller from the two scheduling variables by pole placement, such that the closed-loop poles are the same at every operating point. The set-point weights `wp = wd = 0` let the reference enter through the integral part only, so the response to a reference step is determined by the closed-loop poles alone. The controller tracks a square-wave pitch reference, so the step response of the closed loop is exercised at several operating points along the trajectory. The scheduling logic is contained entirely in `PitchGainSchedule`; the controller itself only receives the gains as input signals.
"""
component GainScheduledPitchControl
  "Air density at sea level [kg/m³]"
  parameter rho0::Real = 1.225
  "Scale height of the exponential atmosphere [m]"
  parameter H::Real = 8500
  "Pitch acceleration per unit elevator deflection and unit dynamic pressure"
  parameter c_u::Real = 6e-4
  "Pitch acceleration per unit pitch angle and unit dynamic pressure"
  parameter c_theta::Real = 4e-4
  "Pitch damping coefficient"
  parameter c_omega::Real = 7.4e-3
  "Maximum elevator deflection [rad]"
  parameter u_max::Real = 0.4
  reference = DiscreteComponents.Examples.PitchReference(amplitude = 0.1, period = 20, start_time = 1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 0, "y1": 450, "x2": 100, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
  altitude = BlockComponents.Sources.Ramp(offset = 0, height = 12000, duration = 100, start_time = 5) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 0, "y1": 50, "x2": 100, "y2": 150, "rot": 0}
      },
      "tags": []
    }
  }
  airspeed = BlockComponents.Sources.Ramp(offset = 250, height = -140, duration = 100, start_time = 5) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 0, "y1": 250, "x2": 100, "y2": 350, "rot": 0}
      },
      "tags": []
    }
  }
  periodicclock = DiscreteComponents.PeriodicClock(dt = 0.02) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 200, "y1": 650, "x2": 300, "y2": 750, "rot": 0}
      },
      "tags": []
    }
  }
  sampler_r = DiscreteComponents.Sampler() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 200, "y1": 450, "x2": 300, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
  sampler_h = DiscreteComponents.Sampler() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 200, "y1": 50, "x2": 300, "y2": 150, "rot": 0}
      },
      "tags": []
    }
  }
  sampler_V = DiscreteComponents.Sampler() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 200, "y1": 250, "x2": 300, "y2": 350, "rot": 0}
      },
      "tags": []
    }
  }
  sampler_theta = DiscreteComponents.Sampler() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 700, "y1": 650, "x2": 600, "y2": 750, "rot": 0}
      },
      "tags": []
    }
  }
  schedule = DiscreteComponents.Examples.PitchGainSchedule(final rho0 = rho0, final H = H, final c_u = c_u, final c_theta = c_theta, final c_omega = c_omega, omega = 4, zeta = 0.7, alpha = 1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 400, "y1": 150, "x2": 500, "y2": 250, "rot": 0}
      },
      "tags": []
    }
  }
  pid = DiscreteComponents.DiscretePIDScheduled(with_I = true, with_D = true, final y_max = u_max, final y_min = -u_max, Nd = 10, wp = 0, wd = 0) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 400, "y1": 450, "x2": 500, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
  zeroorderhold = DiscreteComponents.ZeroOrderHold() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 600, "y1": 450, "x2": 700, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
  aircraft = DiscreteComponents.Examples.AircraftPitch(final rho0 = rho0, final H = H, final c_u = c_u, final c_theta = c_theta, final c_omega = c_omega) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 800, "y1": 450, "x2": 900, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
relations
  connect(reference.y, sampler_r.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(sampler_r.y, pid.u_s, periodicclock.y) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [{"x": 350, "y": 472}], "E": 2},
        {
          "S": 3,
          "M": [{"x": 180, "y": 700}, {"x": 180, "y": 620}, {"x": 350, "y": 620}],
          "E": -1
        }
      ],
      "junctions": [{"x": 350, "y": 500}],
      "renderStyle": "standard"
    }
  }
  connect(altitude.y, sampler_h.u, aircraft.h) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [], "E": 2},
        {"S": -1, "M": [{"x": 150, "y": 20}, {"x": 825, "y": 20}], "E": 3}
      ],
      "junctions": [{"x": 150, "y": 100}],
      "renderStyle": "standard"
    }
  }
  connect(airspeed.y, sampler_V.u, aircraft.V) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [], "E": 2},
        {"S": -1, "M": [{"x": 150, "y": 390}, {"x": 875, "y": 390}], "E": 3}
      ],
      "junctions": [{"x": 150, "y": 300}],
      "renderStyle": "standard"
    }
  }
  connect(sampler_h.y, schedule.h) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 350, "y": 100}, {"x": 350, "y": 175}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(sampler_V.y, schedule.V) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 350, "y": 300}, {"x": 350, "y": 225}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(schedule.K, pid.K) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(schedule.Ti, pid.Ti) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(schedule.Td, pid.Td) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(pid.y, zeroorderhold.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(zeroorderhold.y, aircraft.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(aircraft.theta, sampler_theta.u) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 950, "y": 500}, {"x": 950, "y": 700}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(sampler_theta.y, pid.u_m) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 380, "y": 700}, {"x": 380, "y": 524}], "E": 2}],
      "renderStyle": "standard"
    }
  }
metadata {
  "Dyad": {
    "experiments": {},
    "tests": {},
    "labels": [],
    "icons": {"default": "dyad://Dyad/Default.svg"},
    "path": {},
    "doc": {"behavior": true}
  }
}
end


Test Cases

No test cases defined.

  • Examples
  • Experiments
  • Analyses