Skip to content
LIBRARY
Continuous.PID.md

Continuous.PID

Two-degree-of-freedom PID controller with setpoint weighting (no output limiting).

Acts on the setpoint u_s and measurement u_m, with independent setpoint weighting on each term:

The proportional term sees wp*u_s - u_m and the derivative term sees wd*u_s - u_m (set wd = 0 to remove derivative kick on setpoint steps), while the integral term always sees the full error u_s - u_m so steady-state error is driven to zero. The derivative is filtered with time constant Td/Nd.

The integral and derivative parts can be removed structurally with the with_I and with_D parameters, turning the controller into a P, PI, or PD controller. A structurally removed part contributes no state variables, unlike, e.g., setting Td = 0, which would leave the derivative filter in place with a vanishingly small time constant and cause extreme stiffness.

This block has no output limiter and, consequently, no anti-windup (there is no saturation for the integrator to wind up against). It therefore differs from Modelica.Blocks.Continuous.PID — a single-input text-book PID — and from BlockComponents.Continuous.LimPID, which adds output limiting, feed-forward, and back-calculation anti-windup. Feed-forward is intentionally omitted here: its purpose is to enter before the limiter, so on an unlimited controller it is just an external addition to the output and can be applied with a separate Add block when needed. Use this block when the actuator does not saturate; use LimPID when the output must be clamped.

This component extends from BlockComponents.Interfaces.SingleVariableController

Usage

BlockComponents.Continuous.PID(k=1, Ti=0.5, Td=0.1, wp=1, wd=1, Nd=10, xi0=0, xd0=0)

Parameters:

NameDescriptionUnitsDefault value
with_IWhether or not to include the integral parttrue
with_DWhether or not to include the derivative parttrue
kGain of controller1
TiTime constant of the integrator block (only active if with_I = true)s0.5
TdTime constant of the derivative block (only active if with_D = true)s0.1
wpSet-point weight for proportional block1
wdSet-point weight for derivative block (only active if with_D = true)1
NdMaximum derivative gain. Higher the value of Nd, the more ideal the derivative block gets (less filtering, higher high-frequency gain). (only active if with_D = true)10
xi0Initial guess value for integrator output (only active if with_I = true)0
xd0Initial guess value for derivative output (only active if with_D = true)0

Connectors

  • u_s - This connector represents a real signal as an input to a component (RealInput)

  • u_m - This connector represents a real signal as an input to a component (RealInput)

  • y - This connector represents a real signal as an output from a component (RealOutput)

Variables

NameDescriptionUnits
control_error

Behavior

Source

dyad
"""
Two-degree-of-freedom PID controller with setpoint weighting (no output limiting).

Acts on the setpoint `u_s` and measurement `u_m`, with independent setpoint
weighting on each term:

```math
y = k \left[ (w_p u_s - u_m) + \dfrac{1}{T_i s}(u_s - u_m) + \dfrac{T_d s}{1 + \dfrac{T_d}{N_d}s}(w_d u_s - u_m) \right]
```

The proportional term sees `wp*u_s - u_m` and the derivative term sees
`wd*u_s - u_m` (set `wd = 0` to remove derivative kick on setpoint steps), while
the integral term always sees the full error `u_s - u_m` so steady-state error is
driven to zero. The derivative is filtered with time constant `Td/Nd`.

The integral and derivative parts can be removed structurally with the `with_I`
and `with_D` parameters, turning the controller into a P, PI, or PD controller.
A structurally removed part contributes no state variables, unlike, e.g.,
setting `Td = 0`, which would leave the derivative filter in place with a
vanishingly small time constant and cause extreme stiffness.

This block has no output limiter and, consequently, no anti-windup (there is no
saturation for the integrator to wind up against). It therefore differs from
`Modelica.Blocks.Continuous.PID` — a single-input text-book PID — and from
`BlockComponents.Continuous.LimPID`, which adds output limiting, feed-forward,
and back-calculation anti-windup. Feed-forward is intentionally omitted here: its
purpose is to enter *before* the limiter, so on an unlimited controller it is
just an external addition to the output and can be applied with a separate `Add`
block when needed. Use this block when the actuator does not saturate; use
`LimPID` when the output must be clamped.
"""
component PID
  extends BlockComponents.Interfaces.SingleVariableController
  add_p = BlockComponents.Math.Add(final k1 = wp, final k2 = -1) {
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 100, "y1": 100, "x2": 200, "y2": 200, "rot": 0},
        "diagram": {"iconName": "input", "x1": 100, "y1": 90, "x2": 200, "y2": 190, "rot": 0}
      },
      "tags": []
    }
  }
  add_d = BlockComponents.Math.Add(final k1 = wd, final k2 = -1) if with_D {
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 100, "y1": 300, "x2": 200, "y2": 400}}
    }
  }
  proportional = BlockComponents.Math.Gain(final k = 1) {
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 300, "y1": 100, "x2": 400, "y2": 200, "rot": 0},
        "diagram": {"iconName": "input", "x1": 300, "y1": 90, "x2": 400, "y2": 190, "rot": 0}
      },
      "tags": []
    }
  }
  derivative = Derivative(final k = Td, final T = max(Td / Nd, 1e-14), final x0 = xd0) if with_D {
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 300, "x2": 400, "y2": 400}}
    }
  }
  integrator = Integrator(final k = 1 / Ti, final x0 = xi0) if with_I {
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 500, "x2": 400, "y2": 600}}
    }
  }
  "Zero contribution of the structurally removed derivative part"
  zero_d = BlockComponents.Sources.Constant(final k = 0) if !with_D {
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 300, "x2": 400, "y2": 400}}
    }
  }
  "Zero contribution of the structurally removed integral part"
  zero_i = BlockComponents.Sources.Constant(final k = 0) if !with_I {
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 500, "x2": 400, "y2": 600}}
    }
  }
  add_pid = BlockComponents.Math.Add3(final k1 = 1, final k2 = 1, final k3 = 1) {
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 500, "y1": 300, "x2": 600, "y2": 400, "rot": 0},
        "diagram": {"iconName": "input", "x1": 500, "y1": 120, "x2": 600, "y2": 220, "rot": 0}
      },
      "tags": []
    }
  }
  gain_pid = BlockComponents.Math.Gain(final k = k) {
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 700, "y1": 300, "x2": 800, "y2": 400, "rot": 0},
        "diagram": {"iconName": "input", "x1": 550, "y1": 310, "x2": 650, "y2": 410, "rot": 0}
      },
      "tags": []
    }
  }
  variable control_error::Real
  "Whether or not to include the integral part"
  structural parameter with_I::Boolean = true
  "Whether or not to include the derivative part"
  structural parameter with_D::Boolean = true
  "Gain of controller"
  parameter k::Real = 1
  "Time constant of the integrator block (only active if `with_I = true`)"
  parameter Ti::Time = 0.5
  "Time constant of the derivative block (only active if `with_D = true`)"
  parameter Td::Time = 0.1
  "Set-point weight for proportional block"
  parameter wp::Real = 1
  "Set-point weight for derivative block (only active if `with_D = true`)"
  parameter wd::Real = 1
  "Maximum derivative gain. Higher the value of `Nd`, the more ideal the derivative block gets (less filtering, higher high-frequency gain). (only active if `with_D = true`)"
  parameter Nd::Real = 10
  "Initial guess value for integrator output (only active if `with_I = true`)"
  parameter xi0::Real = 0
  "Initial guess value for derivative output (only active if `with_D = true`)"
  parameter xd0::Real = 0
relations
  connect(add_p.y, proportional.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(proportional.y, add_pid.u1) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(add_pid.y, gain_pid.u) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 650, "y": 170},
            {"x": 650, "y": 260},
            {"x": 510, "y": 260},
            {"x": 510, "y": 360}
          ],
          "E": 2
        }
      ],
      "renderStyle": "standard"
    }
  }
  connect(gain_pid.y, y) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  control_error = u_s - u_m
  connect(add_p.u1, u_s) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 20, "y": 110}, {"x": 20, "y": 270}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(u_m, add_p.u2) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 50, "y": 730}, {"x": 50, "y": 170}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  if with_D
    connect(add_d.y, derivative.u) {"Dyad": {"edges": [{"S": 1, "E": 2}]}}
    connect(derivative.y, add_pid.u2) {
      "Dyad": {
        "edges": [{"S": 1, "M": [{"x": 452.5, "y": 350}, {"x": 452.5, "y": 170}], "E": 2}],
        "renderStyle": "standard"
      }
    }
    connect(add_d.u1, u_s) {
      "Dyad": {
        "edges": [{"S": 1, "M": [{"x": 20, "y": 320}, {"x": 20, "y": 270}], "E": 2}],
        "renderStyle": "standard"
      }
    }
    connect(add_d.u2, u_m) {
      "Dyad": {
        "edges": [{"S": 1, "M": [{"x": 50, "y": 380}, {"x": 50, "y": 730}], "E": 2}],
        "renderStyle": "standard"
      }
    }
  else
    connect(zero_d.y, add_pid.u2) {"Dyad": {"edges": [{"S": 1, "E": 2}]}}
  end
  if with_I
    integrator.u = control_error
    connect(integrator.y, add_pid.u3) {
      "Dyad": {
        "edges": [{"S": 1, "M": [{"x": 490, "y": 550}, {"x": 490, "y": 200}], "E": 2}],
        "renderStyle": "standard"
      }
    }
  else
    connect(zero_i.y, add_pid.u3) {"Dyad": {"edges": [{"S": 1, "E": 2}]}}
  end
metadata {
  "Dyad": {
    "labels": [{"label": "$(instance)", "x": 500, "y": 1100, "rot": 0}],
    "icons": {"default": "dyad://BlockComponents/PID.svg"}
  }
}
end
Flattened Source
dyad
"""
Two-degree-of-freedom PID controller with setpoint weighting (no output limiting).

Acts on the setpoint `u_s` and measurement `u_m`, with independent setpoint
weighting on each term:

```math
y = k \left[ (w_p u_s - u_m) + \dfrac{1}{T_i s}(u_s - u_m) + \dfrac{T_d s}{1 + \dfrac{T_d}{N_d}s}(w_d u_s - u_m) \right]
```

The proportional term sees `wp*u_s - u_m` and the derivative term sees
`wd*u_s - u_m` (set `wd = 0` to remove derivative kick on setpoint steps), while
the integral term always sees the full error `u_s - u_m` so steady-state error is
driven to zero. The derivative is filtered with time constant `Td/Nd`.

The integral and derivative parts can be removed structurally with the `with_I`
and `with_D` parameters, turning the controller into a P, PI, or PD controller.
A structurally removed part contributes no state variables, unlike, e.g.,
setting `Td = 0`, which would leave the derivative filter in place with a
vanishingly small time constant and cause extreme stiffness.

This block has no output limiter and, consequently, no anti-windup (there is no
saturation for the integrator to wind up against). It therefore differs from
`Modelica.Blocks.Continuous.PID` — a single-input text-book PID — and from
`BlockComponents.Continuous.LimPID`, which adds output limiting, feed-forward,
and back-calculation anti-windup. Feed-forward is intentionally omitted here: its
purpose is to enter *before* the limiter, so on an unlimited controller it is
just an external addition to the output and can be applied with a separate `Add`
block when needed. Use this block when the actuator does not saturate; use
`LimPID` when the output must be clamped.
"""
component PID
  "Connector of setpoint input signal"
  u_s = RealInput() {
    "Dyad": {
      "placement": {
        "icon": {"iconName": "default", "x1": -100, "y1": 220, "x2": 0, "y2": 320, "rot": 0},
        "diagram": {"iconName": "default", "x1": -100, "y1": 220, "x2": 0, "y2": 320, "rot": 0}
      }
    }
  }
  "Connector of measurement input signal"
  u_m = RealInput() {
    "Dyad": {
      "placement": {
        "icon": {"iconName": "default", "x1": -100, "y1": 680, "x2": 0, "y2": 780, "rot": 0},
        "diagram": {"iconName": "default", "x1": -100, "y1": 680, "x2": 0, "y2": 780, "rot": 0}
      }
    }
  }
  "Connector of actuator output signal"
  y = RealOutput() {
    "Dyad": {
      "placement": {
        "icon": {"iconName": "output", "x1": 1000, "y1": 450, "x2": 1100, "y2": 550, "rot": 0},
        "diagram": {"iconName": "output", "x1": 1000, "y1": 450, "x2": 1100, "y2": 550, "rot": 0}
      }
    }
  }
  add_p = BlockComponents.Math.Add(final k1 = wp, final k2 = -1) {
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 100, "y1": 100, "x2": 200, "y2": 200, "rot": 0},
        "diagram": {"iconName": "input", "x1": 100, "y1": 90, "x2": 200, "y2": 190, "rot": 0}
      },
      "tags": []
    }
  }
  add_d = BlockComponents.Math.Add(final k1 = wd, final k2 = -1) if with_D {
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 100, "y1": 300, "x2": 200, "y2": 400}}
    }
  }
  proportional = BlockComponents.Math.Gain(final k = 1) {
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 300, "y1": 100, "x2": 400, "y2": 200, "rot": 0},
        "diagram": {"iconName": "input", "x1": 300, "y1": 90, "x2": 400, "y2": 190, "rot": 0}
      },
      "tags": []
    }
  }
  derivative = Derivative(final k = Td, final T = max(Td / Nd, 1e-14), final x0 = xd0) if with_D {
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 300, "x2": 400, "y2": 400}}
    }
  }
  integrator = Integrator(final k = 1 / Ti, final x0 = xi0) if with_I {
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 500, "x2": 400, "y2": 600}}
    }
  }
  "Zero contribution of the structurally removed derivative part"
  zero_d = BlockComponents.Sources.Constant(final k = 0) if !with_D {
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 300, "x2": 400, "y2": 400}}
    }
  }
  "Zero contribution of the structurally removed integral part"
  zero_i = BlockComponents.Sources.Constant(final k = 0) if !with_I {
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 500, "x2": 400, "y2": 600}}
    }
  }
  add_pid = BlockComponents.Math.Add3(final k1 = 1, final k2 = 1, final k3 = 1) {
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 500, "y1": 300, "x2": 600, "y2": 400, "rot": 0},
        "diagram": {"iconName": "input", "x1": 500, "y1": 120, "x2": 600, "y2": 220, "rot": 0}
      },
      "tags": []
    }
  }
  gain_pid = BlockComponents.Math.Gain(final k = k) {
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 700, "y1": 300, "x2": 800, "y2": 400, "rot": 0},
        "diagram": {"iconName": "input", "x1": 550, "y1": 310, "x2": 650, "y2": 410, "rot": 0}
      },
      "tags": []
    }
  }
  variable control_error::Real
  "Whether or not to include the integral part"
  structural parameter with_I::Boolean = true
  "Whether or not to include the derivative part"
  structural parameter with_D::Boolean = true
  "Gain of controller"
  parameter k::Real = 1
  "Time constant of the integrator block (only active if `with_I = true`)"
  parameter Ti::Time = 0.5
  "Time constant of the derivative block (only active if `with_D = true`)"
  parameter Td::Time = 0.1
  "Set-point weight for proportional block"
  parameter wp::Real = 1
  "Set-point weight for derivative block (only active if `with_D = true`)"
  parameter wd::Real = 1
  "Maximum derivative gain. Higher the value of `Nd`, the more ideal the derivative block gets (less filtering, higher high-frequency gain). (only active if `with_D = true`)"
  parameter Nd::Real = 10
  "Initial guess value for integrator output (only active if `with_I = true`)"
  parameter xi0::Real = 0
  "Initial guess value for derivative output (only active if `with_D = true`)"
  parameter xd0::Real = 0
relations
  connect(add_p.y, proportional.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(proportional.y, add_pid.u1) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(add_pid.y, gain_pid.u) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 650, "y": 170},
            {"x": 650, "y": 260},
            {"x": 510, "y": 260},
            {"x": 510, "y": 360}
          ],
          "E": 2
        }
      ],
      "renderStyle": "standard"
    }
  }
  connect(gain_pid.y, y) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  control_error = u_s - u_m
  connect(add_p.u1, u_s) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 20, "y": 110}, {"x": 20, "y": 270}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(u_m, add_p.u2) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 50, "y": 730}, {"x": 50, "y": 170}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  if with_D
    connect(add_d.y, derivative.u) {"Dyad": {"edges": [{"S": 1, "E": 2}]}}
    connect(derivative.y, add_pid.u2) {
      "Dyad": {
        "edges": [{"S": 1, "M": [{"x": 452.5, "y": 350}, {"x": 452.5, "y": 170}], "E": 2}],
        "renderStyle": "standard"
      }
    }
    connect(add_d.u1, u_s) {
      "Dyad": {
        "edges": [{"S": 1, "M": [{"x": 20, "y": 320}, {"x": 20, "y": 270}], "E": 2}],
        "renderStyle": "standard"
      }
    }
    connect(add_d.u2, u_m) {
      "Dyad": {
        "edges": [{"S": 1, "M": [{"x": 50, "y": 380}, {"x": 50, "y": 730}], "E": 2}],
        "renderStyle": "standard"
      }
    }
  else
    connect(zero_d.y, add_pid.u2) {"Dyad": {"edges": [{"S": 1, "E": 2}]}}
  end
  if with_I
    integrator.u = control_error
    connect(integrator.y, add_pid.u3) {
      "Dyad": {
        "edges": [{"S": 1, "M": [{"x": 490, "y": 550}, {"x": 490, "y": 200}], "E": 2}],
        "renderStyle": "standard"
      }
    }
  else
    connect(zero_i.y, add_pid.u3) {"Dyad": {"edges": [{"S": 1, "E": 2}]}}
  end
metadata {
  "Dyad": {
    "labels": [{"label": "$(instance)", "x": 500, "y": 1100, "rot": 0}],
    "icons": {"default": "dyad://BlockComponents/PID.svg"}
  }
}
end


Test Cases

No test cases defined.