朝、シャワーを浴びるとき、あなたは無意識に「PID制御」を行っています。蛇口をひねってお湯を出し、手に当たる温度が冷たければもっと熱い方に回し、熱すぎればすぐに戻す。このとき、あなたの脳は「今の温度と理想の温度のズレ(偏差)」を感じ取り、「どのくらい蛇口を回すか」を瞬時に判断しています。これこそが、フィードバック制御の本質です。
では、この「温度のズレに応じて蛇口を回す」という操作を、機械やコンピュータに自動で行わせるにはどうすればよいでしょうか。ここで登場するのが PID制御(Proportional-Integral-Derivative control)です。PID制御は、産業界で最も広く使われている制御手法であり、全フィードバック制御ループの90%以上がPID制御器で構成されていると言われています。
PID制御を理解すると、以下のような幅広い分野で応用が効きます。
- 温度制御: エアコン、工業炉、3Dプリンタのヒートベッドなど、設定温度を正確に維持する場面
- モーター制御: ドローンのモーター回転数制御、ロボットアームの位置決め、電気自動車の速度制御
- プロセス制御: 化学プラントの圧力・流量制御、半導体製造装置の精密制御
- 航空宇宙: 人工衛星の姿勢制御、ロケットの推力方向制御(ジンバル制御)
PID制御の強みは、構造がシンプルでありながら、適切にパラメータを調整(チューニング)すれば、多くの制御対象に対して良好な性能を発揮できる点です。3つのパラメータ(P・I・D)の意味を直感的に理解し、それぞれがシステムにどのような影響を与えるかを把握すれば、実際の設計場面で「どのパラメータをどう調整すればよいか」という判断ができるようになります。
本記事の内容
- P・I・D各要素の直感的な理解と数学的定義
- PID制御器の伝達関数と閉ループ系の構成
- P制御の限界(定常偏差が残る理由)
- I制御の効果と副作用(ワインドアップ問題)
- D制御の予測的な性質
- ジーグラー・ニコルス法によるチューニング手法
- Pythonによる P / PI / PID の比較シミュレーションと考察
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
フィードバック制御とPID制御の位置づけ
PID制御の中身に入る前に、まず「フィードバック制御」の全体像を押さえておきましょう。
フィードバック制御とは、出力を測定して目標値と比較し、その差(偏差)に基づいて入力を調整する仕組みです。エアコンが室温を測りながら運転を調整するのは、フィードバック制御の典型例です。詳しくはフィードバック制御の基本を参照してください。
フィードバック制御における重要な問いは、「偏差を受け取ったとき、どのような操作量を出力すればよいか」です。この「偏差から操作量への変換ルール」を決めるのが 制御器(コントローラ) であり、PID制御器はその最も代表的な設計方法です。
PID制御器は、偏差に対して3つの異なる処理を行い、それらを足し合わせて操作量を決定します。
- P(比例): 今の偏差の大きさに比例した操作
- I(積分): 過去の偏差の蓄積に比例した操作
- D(微分): 偏差の変化速度に比例した操作
この3つを組み合わせることで、「素早く」「正確に」「安定して」目標値に追従するシステムを設計できます。まずは、PID制御器の全体像を数式で定義してから、各要素の役割を一つずつ掘り下げていきましょう。
PID制御器の数学的定義
時間領域での表現
PID制御器の出力 $u(t)$ は、偏差 $e(t) = r(t) – y(t)$(目標値 $r(t)$ と出力 $y(t)$ の差)に対して、次の3つの項の和で表されます。
$$ \begin{equation} u(t) = \underbrace{K_p \, e(t)}_{\text{比例項}} + \underbrace{K_i \int_0^t e(\tau) \, d\tau}_{\text{積分項}} + \underbrace{K_d \frac{de(t)}{dt}}_{\text{微分項}} \end{equation} $$
ここで、各パラメータの意味は以下のとおりです。
| パラメータ | 名称 | 役割 |
|---|---|---|
| $K_p$ | 比例ゲイン | 偏差の「今の大きさ」に対する反応の強さ |
| $K_i$ | 積分ゲイン | 偏差の「過去の蓄積」に対する反応の強さ |
| $K_d$ | 微分ゲイン | 偏差の「変化速度」に対する反応の強さ |
この式は、直感的には「現在・過去・未来の3つの視点から偏差を評価している」と考えることができます。P項は「今どれだけズレているか」を見て、I項は「これまでどれだけズレが溜まっているか」を見て、D項は「これからどちらに向かっているか」を予測しています。
ラプラス変換による伝達関数表現
時間領域の式をラプラス変換すると、PID制御器の伝達関数が得られます。
時間領域での積分 $\int_0^t e(\tau) d\tau$ はラプラス領域で $E(s)/s$ に、微分 $de(t)/dt$ は $sE(s)$ に対応するので、
$$ \begin{equation} C(s) = \frac{U(s)}{E(s)} = K_p + \frac{K_i}{s} + K_d s \end{equation} $$
と書けます。ここで、$K_p = K_i T_i = K_d / T_d$ という関係を使って、積分時間 $T_i$ と微分時間 $T_d$ で書き換えると、
$$ \begin{equation} C(s) = K_p\left(1 + \frac{1}{T_i s} + T_d s\right) \end{equation} $$
という形にもなります。ここで $T_i = K_p / K_i$ は 積分時間、$T_d = K_d / K_p$ は 微分時間 と呼ばれます。この「標準形」は、ジーグラー・ニコルス法などのチューニング法で頻繁に登場するので、両方の書き方に慣れておくことが大切です。
それでは、P・I・Dの各要素がシステムにどのような効果をもたらすのか、一つずつ詳しく見ていきましょう。
P制御(比例制御)— 今の偏差に即座に反応する
直感的な理解
P制御は、PID制御の中で最もシンプルで直感的な要素です。日常で言えば、車を運転しているときに、前の車との車間距離が縮まったらブレーキを踏み、離れたらアクセルを踏むという操作に相当します。
ポイントは、「ズレが大きいほど、強く修正する」ということです。車間距離が少しだけ短ければ軽くブレーキを踏み、大きく縮まっていたら強くブレーキを踏みます。偏差の大きさに「比例」した操作量を出す、これがP制御の本質です。
数学的定義
P制御の出力は、偏差 $e(t)$ に比例ゲイン $K_p$ を掛けたものです。
$$ \begin{equation} u_P(t) = K_p \, e(t) \end{equation} $$
$K_p$ が大きいほど、同じ偏差に対して大きな操作量を出します。つまり、「敏感に反応する」ことになります。
P制御の効果と限界
$K_p$ を大きくすると、偏差に対する応答が速くなります。しかし、$K_p$ を大きくしすぎると、システムが振動的になり、最悪の場合は不安定になります。これは、2次遅れ系の応答で見られる減衰比の低下と同じ現象です。
さらに重要なのは、P制御だけでは 定常偏差(オフセット) が残ってしまうことです。なぜでしょうか? 直感的に考えてみましょう。
P制御は「偏差があるときだけ操作量を出す」仕組みです。裏を返すと、偏差がゼロになった瞬間、操作量もゼロになってしまいます。しかし、多くの制御対象(例えば、重力に逆らって物体を持ち上げ続ける場面)では、目標値を維持するためにゼロでない操作量が必要です。
この矛盾を解消するために、P制御は「偏差が少し残った状態」で釣り合いを取ります。つまり、偏差を完全にゼロにはできず、ある程度の偏差が残った状態で定常状態に落ち着くのです。これが 定常偏差 です。
数学的には、制御対象が1次遅れ系 $G(s) = 1/(Ts + 1)$ の場合、P制御による閉ループ系のステップ応答の定常値は次のようになります。
最終値定理を適用すると、ステップ入力 $R(s) = 1/s$ に対して、
$$ y(\infty) = \lim_{s \to 0} s \cdot \frac{K_p \cdot G(s)}{1 + K_p \cdot G(s)} \cdot \frac{1}{s} $$
$G(0) = 1$ の場合、
$$ y(\infty) = \frac{K_p}{1 + K_p} $$
となります。目標値が1であるのに対し、定常値は $K_p / (1 + K_p)$ なので、定常偏差は、
$$ e(\infty) = 1 – \frac{K_p}{1 + K_p} = \frac{1}{1 + K_p} $$
です。$K_p$ を大きくすれば定常偏差は小さくなりますが、決してゼロにはなりません。$K_p \to \infty$ でようやく $e(\infty) \to 0$ ですが、実際にはゲインを無限大にはできませんし、そうすると振動や不安定の問題が生じます。
この「P制御では定常偏差が消えない」という本質的な限界を克服するために、積分制御(I制御)が必要になります。
I制御(積分制御)— 過去の偏差の蓄積に応じて修正する
直感的な理解
I制御を理解するために、お風呂のお湯の温度調節を考えてみましょう。
蛇口をある位置に固定したところ、お湯の温度が目標より少しだけ低かったとします。P制御だけなら、「少しだけ低い」という情報に基づいてわずかに蛇口を回しますが、それでもまだ少し低い状態が続くかもしれません(定常偏差)。
ここで、人間は「もうずっと温度が低いままだ。少しずつもっと蛇口を回そう」と考えます。つまり、偏差が長時間続いているという「蓄積された不満」に基づいて、操作量を増やしていくのです。これがI制御の役割です。
数学的定義
I制御の出力は、偏差の時間積分に積分ゲイン $K_i$ を掛けたものです。
$$ \begin{equation} u_I(t) = K_i \int_0^t e(\tau) \, d\tau \end{equation} $$
積分とは、偏差を時間方向に足し合わせる操作です。偏差がわずかでも正(目標値より低い)の状態が続けば、積分値はじわじわと増加し、それに伴って操作量も増え続けます。操作量が増えれば出力が上がり、やがて偏差がゼロになります。偏差がゼロになると積分値の増加が止まり、そのときの操作量が定常的に維持されます。
I制御による定常偏差の除去
I制御が定常偏差を除去できる理由を、もう少し丁寧に見てみましょう。
仮に、定常状態で偏差 $e(\infty) \neq 0$ が残っていたとします。すると、積分項 $\int_0^t e(\tau) d\tau$ は時間とともに際限なく増加します。積分項が増え続ければ操作量 $u(t)$ も増え続けるので、出力 $y(t)$ も変化し続けます。これは「定常状態」と矛盾します。したがって、定常状態が存在するためには $e(\infty) = 0$ でなければなりません。これが、I制御が定常偏差を原理的に除去できる理由です。
伝達関数の観点から見ると、I制御は原点に極を持つ $K_i / s$ を含みます。これにより、開ループ伝達関数のシステムタイプが1つ上がり(0型→1型)、ステップ入力に対する定常偏差がゼロになります。システムタイプと定常偏差の関係については、フィードバック制御の基本も参照してください。
I制御の副作用: ワインドアップ問題
I制御には重要な副作用があります。それが 積分ワインドアップ(integral windup) です。
実際のシステムでは、操作量には物理的な上限と下限があります。例えば、バルブの開度は0%から100%、モーターの電圧は0Vから12Vなどです。操作量が飽和(上限または下限に張り付く)している状態でも、偏差が残っていれば積分項は増え続けます。
操作量が飽和しているので、積分値がいくら増えても実際の操作量は変わりません。しかし、積分値は内部で膨大な値に膨らんでしまいます。その後、目標値が変わって偏差の符号が反転しても、膨らんだ積分値が減少するまでに長い時間がかかるため、操作量が飽和したまま応答が遅れるという問題が生じます。これがワインドアップです。
ワインドアップへの対策としては、以下の方法が一般的に用いられます。
- 積分項のクランプ: 積分値に上限・下限を設けて、一定範囲を超えないようにする
- アンチワインドアップ: 操作量が飽和しているときは積分の更新を停止する、または飽和量に応じて積分値を巻き戻す
- 条件付き積分: 偏差が一定値以下のときだけ積分を有効にする
I制御は定常偏差を除去するという強力な効果を持ちますが、応答を遅くしたり、オーバーシュートを増大させたりする傾向もあります。特に $K_i$ が大きいと、積分項が過剰に蓄積してオーバーシュートが大きくなります。この振動的な挙動を抑えるのが、次に説明するD制御です。
D制御(微分制御)— 未来の偏差を予測して先手を打つ
直感的な理解
D制御の直感を掴むために、坂道を車で下る場面を想像してみてください。
前方に急カーブが見えてきました。まだカーブには到達していないので、今の時点では車線からのズレ(偏差)はありません。しかし、このまま何もしなければ大きくはみ出すことが予想されます。あなたは、偏差が大きくなる「前に」ブレーキを踏みます。これがD制御の本質です。
P制御が「今の偏差」に反応し、I制御が「過去の偏差の蓄積」に反応するのに対して、D制御は 偏差の変化速度(微分) を見ることで、「これからどうなりそうか」を予測して先手を打ちます。偏差が急速に増えていれば大きな修正操作を出し、偏差がゆっくり減っていれば修正を緩めます。
数学的定義
D制御の出力は、偏差の時間微分に微分ゲイン $K_d$ を掛けたものです。
$$ \begin{equation} u_D(t) = K_d \frac{de(t)}{dt} \end{equation} $$
偏差 $e(t)$ が増加している($de/dt > 0$)ときは正の操作量を出して先回りし、偏差が減少している($de/dt < 0$)ときは操作量を減らしてブレーキをかけます。
D制御の効果
D制御の主な効果は オーバーシュートの抑制 と 振動の減衰 です。
PI制御だけでは、出力が目標値に向かって勢いよく近づいたとき、「偏差が急速に小さくなっている」にもかかわらず操作量がまだ大きいため、目標値を通り越してしまう(オーバーシュートする)ことがあります。D制御は、「偏差が急速に小さくなっている」という情報を検知して操作量を減らすため、目標値の手前で減速するような効果を生みます。
2次遅れ系の応答の観点から見ると、D制御は系の減衰比を増加させる効果があります。減衰比が大きくなると、振動的な応答が抑制され、より滑らかに目標値に収束します。
D制御の注意点: ノイズへの敏感さ
D制御には実用上の重要な注意点があります。微分操作はノイズを増幅するのです。
実際のセンサ出力には必ず高周波のノイズが含まれています。微分はノイズの急激な変動を拾い、大きな操作量の振動を引き起こします。このため、実用的なPID制御器では、D項に不完全微分(ローパスフィルタ付き微分)を用います。
$$ \begin{equation} C_D(s) = \frac{K_d s}{1 + \frac{K_d}{N K_p} s} \end{equation} $$
ここで $N$ はフィルタ係数(典型的には $N = 8 \sim 20$)です。この形にすると、低周波の信号に対しては通常の微分として動作しますが、高周波ノイズに対してはゲインが飽和して増幅を防ぎます。
ここまでで、P・I・D各要素の役割と特性を理解しました。次に、これら3つの要素を組み合わせたときの効果を表形式で整理し、パラメータ調整の指針を得ましょう。
P・I・D各要素の効果まとめ
PID制御の各パラメータを増加させたときに、システム応答の特性がどのように変化するかを表にまとめます。この表は、実際にパラメータを調整する際の出発点として役立ちます。
| パラメータ | 立ち上がり時間 | オーバーシュート | 整定時間 | 定常偏差 |
|---|---|---|---|---|
| $K_p$ 増加 | 短くなる | 増加する | 変化小 | 減少するが消えない |
| $K_i$ 増加 | 短くなる | 増加する | 長くなる傾向 | 消える |
| $K_d$ 増加 | 変化小 | 減少する | 短くなる傾向 | 変化なし |
ここで各用語を確認しておきましょう。
- 立ち上がり時間: 出力が定常値の10%から90%に達するまでの時間。小さいほど応答が速い
- オーバーシュート: 出力が目標値を超えた量の最大値。小さいほど滑らか
- 整定時間: 出力が目標値の一定範囲(通常 $\pm 2\%$ または $\pm 5\%$)内に収まるまでの時間
- 定常偏差: 十分時間が経った後に残る、目標値と出力の差
この表からわかるように、P・I・Dの各パラメータはそれぞれ異なるトレードオフを持っています。例えば、$K_p$ を大きくすると応答は速くなりますが、オーバーシュートが増えます。$K_i$ を入れると定常偏差は消えますが、整定時間が長くなりがちです。$K_d$ を加えるとオーバーシュートを抑えられますが、ノイズに敏感になります。
良いPID制御器を設計するとは、これらのトレードオフのバランスを取り、対象とするシステムの要求仕様を満たすパラメータの組み合わせを見つけることに他なりません。では、閉ループ伝達関数の数式的な構造を確認してから、パラメータを系統的に決めるチューニング手法を見ていきましょう。
閉ループ伝達関数の導出
ブロック線図から閉ループ伝達関数を求める
制御対象の伝達関数を $G(s)$、PID制御器の伝達関数を $C(s)$ とします。ブロック線図で表すと、PID制御系は典型的なフィードバック構成です。
目標値 $R(s)$ に対する出力 $Y(s)$ の閉ループ伝達関数は、以下のように導出できます。
開ループ伝達関数は $L(s) = C(s)G(s)$ です。フィードバックの関係式 $E(s) = R(s) – Y(s)$ と、$Y(s) = L(s)E(s)$ から、
$$ Y(s) = L(s) \bigl(R(s) – Y(s)\bigr) $$
$Y(s)$ について整理すると、
$$ Y(s) + L(s)Y(s) = L(s)R(s) $$
$$ Y(s)\bigl(1 + L(s)\bigr) = L(s)R(s) $$
したがって、閉ループ伝達関数は、
$$ \begin{equation} T(s) = \frac{Y(s)}{R(s)} = \frac{C(s)G(s)}{1 + C(s)G(s)} \end{equation} $$
となります。この式は、制御系設計の中核をなす非常に重要な式です。閉ループ系の安定性は、特性方程式 $1 + C(s)G(s) = 0$ の根(閉ループ極)の位置によって決まります。閉ループ極がすべて複素平面の左半面にあればシステムは安定です。安定性の判別には、ラウス・フルヴィッツの安定判別やナイキスト線図を利用できます。
具体例: 2次系に対するPID制御
制御対象が2次系 $G(s) = 1/(s^2 + s + 1)$ の場合を考えてみましょう。PID制御器 $C(s) = K_p + K_i/s + K_d s$ を適用すると、開ループ伝達関数は、
$$ L(s) = C(s)G(s) = \frac{K_d s^2 + K_p s + K_i}{s(s^2 + s + 1)} $$
です。ここで $C(s) = (K_d s^2 + K_p s + K_i)/s$ と通分しています。
閉ループ伝達関数は、
$$ T(s) = \frac{K_d s^2 + K_p s + K_i}{s^3 + (1+K_d)s^2 + (1+K_p)s + K_i} $$
となります。分母が3次多項式なので、$K_p$, $K_i$, $K_d$ の値によって3つの閉ループ極の位置が変わり、応答特性が決まります。この極の配置は根軌跡法で可視化することもできます。
閉ループ伝達関数の構造がわかったところで、いよいよ「パラメータ $K_p$, $K_i$, $K_d$ をどうやって決めるか」というチューニングの問題に取り組みましょう。
ジーグラー・ニコルス法(限界感度法)
なぜチューニング法が必要か
PID制御器の3つのパラメータ $K_p$, $K_i$, $K_d$ を決める方法には、理論的なアプローチ(極配置法、最適制御など)から実験的なアプローチまで様々なものがあります。しかし、制御対象の正確な数学モデルが得られないことも多く、そのような場合には 実験ベースのチューニング法 が重宝します。
ジーグラー・ニコルス法は、1942年にJohn G. ZieglerとNathaniel B. Nicholsが提案した古典的なチューニング手法で、制御対象の実験データから系統的にPIDパラメータを決定できます。「とりあえずまともなPID制御器を設計したい」というときの出発点として、今日でも広く使われています。
限界感度法の手順
ジーグラー・ニコルス法にはいくつかのバリエーションがありますが、ここでは最もよく使われる 限界感度法(ultimate gain method) を紹介します。
ステップ1: I制御とD制御を無効にする
まず、$K_i = 0$, $K_d = 0$ に設定し、P制御のみの状態にします。
ステップ2: 限界ゲインを見つける
$K_p$ を0から徐々に増加させていきます。$K_p$ が小さいうちは、ステップ応答は減衰して定常値に落ち着きます。しかし、$K_p$ を上げ続けると、あるところで出力が減衰せずに一定振幅で振動し続ける状態(持続振動)に達します。
この持続振動が起きるときの $K_p$ を 限界ゲイン $K_u$(ultimate gain)と呼びます。
ステップ3: 限界周期を測定する
持続振動の1周期の長さ(ピークからピークまでの時間)を測定します。これを 限界周期 $T_u$(ultimate period)と呼びます。
ステップ4: パラメータを算出する
$K_u$ と $T_u$ を以下の表に当てはめて、PIDパラメータを計算します。
| 制御器の種類 | $K_p$ | $T_i$ | $T_d$ |
|---|---|---|---|
| P制御 | $0.5 \, K_u$ | — | — |
| PI制御 | $0.45 \, K_u$ | $T_u / 1.2$ | — |
| PID制御 | $0.6 \, K_u$ | $T_u / 2$ | $T_u / 8$ |
$K_i$ と $K_d$ への変換は、$K_i = K_p / T_i$、$K_d = K_p \cdot T_d$ で行います。
なぜこの方法で良いパラメータが得られるのか
ジーグラー・ニコルス法の背景にある考え方は、ゲイン余裕と位相余裕の概念と密接に関係しています。
限界ゲイン $K_u$ は、閉ループ系が「ちょうど安定の境界」にあるときのゲインです。つまり、ゲイン余裕がちょうど0 dBになる点です。ここからゲインを少し下げることで、適度なゲイン余裕を確保しています。
限界周期 $T_u$ は、位相が $-180°$ になる周波数(ゲイン交差周波数)の情報を含んでいます。この周波数を基準として積分時間 $T_i$ と微分時間 $T_d$ を決めることで、位相余裕も適切に確保される仕組みになっています。
ジーグラー・ニコルスの表に示された係数(0.6, 0.5, 0.125など)は、多くのプロセス制御系に対して経験的に良好な性能を示すように調整されたものです。ただし、この方法はオーバーシュートがやや大きめの応答を与える傾向があるため、オーバーシュートを許容できない用途では、ここで得られた値を出発点としてさらに微調整を行うのが一般的です。
チューニング法の理論を学んだので、次はPythonを使って実際にP制御、PI制御、PID制御の応答を比較するシミュレーションを行い、各要素の効果を目で確認してみましょう。
Pythonシミュレーション: P / PI / PID の比較
ここでは、制御対象として2次系 $G(s) = 1/(s^2 + s + 1)$ を取り上げ、P制御・PI制御・PID制御のステップ応答を比較します。Pythonで制御系シミュレーションの基本的な使い方も参考にしてください。
P制御: 比例ゲインの影響
まず、P制御のみでゲイン $K_p$ を変化させたときの応答を見てみましょう。このシミュレーションにより、「$K_p$ を上げると応答が速くなるが、振動的になり、定常偏差は消えない」ことを確認します。
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# 制御対象: G(s) = 1 / (s^2 + s + 1)
# P制御: C(s) = Kp
# 閉ループ: Kp / (s^2 + s + 1 + Kp)
t = np.linspace(0, 30, 3000)
plt.figure(figsize=(10, 6))
for Kp in [0.5, 1.0, 2.0, 5.0, 10.0]:
num_cl = [Kp]
den_cl = [1, 1, 1 + Kp]
sys_cl = signal.TransferFunction(num_cl, den_cl)
t_out, y_out = signal.step(sys_cl, T=t)
# 定常値の計算
steady_state = Kp / (1 + Kp)
plt.plot(t_out, y_out, linewidth=1.5,
label=f'Kp={Kp} (定常値={steady_state:.3f})')
plt.axhline(y=1, color='k', linestyle='--', alpha=0.3, label='目標値')
plt.xlabel('Time [s]', fontsize=12)
plt.ylabel('Output', fontsize=12)
plt.title('P制御: Kpの影響', fontsize=14)
plt.legend(fontsize=9)
plt.grid(True, alpha=0.3)
plt.xlim([0, 30])
plt.tight_layout()
plt.show()
このグラフからは、以下の重要な特徴が読み取れます。
-
$K_p$ が小さいとき($K_p = 0.5$): 応答は緩やかで、定常値は $0.5/(1+0.5) = 0.333$ と目標値1.0から大きく離れています。定常偏差が非常に大きく、実用的ではありません。
-
$K_p$ を大きくすると: 応答が速くなり、定常値も目標値に近づいていきます。$K_p = 10$ では定常値が $10/11 \approx 0.909$ まで上がりますが、それでも約9%の定常偏差が残っています。また、$K_p$ が大きい場合はオーバーシュートや振動が顕著になっていることも確認できます。
-
定常偏差は決してゼロにならない: どの $K_p$ でも出力が目標値の破線($y = 1$)に到達しないことが、P制御の本質的な限界を視覚的に示しています。
PI制御: 積分項による定常偏差の除去
次に、P制御に積分項を加えたPI制御の効果を確認します。$K_p = 2.0$ に固定し、$K_i$ を変化させます。
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# 制御対象: G(s) = 1 / (s^2 + s + 1)
# PI制御: C(s) = Kp + Ki/s = (Kp*s + Ki) / s
# 閉ループ: (Kp*s + Ki) / (s^3 + s^2 + (1+Kp)*s + Ki)
Kp = 2.0
t = np.linspace(0, 30, 3000)
plt.figure(figsize=(10, 6))
for Ki in [0, 0.5, 1.0, 2.0, 5.0]:
if Ki == 0:
# P制御のみ(比較用)
num_cl = [Kp]
den_cl = [1, 1, 1 + Kp]
label = f'Ki=0 (P制御のみ, 定常偏差あり)'
else:
num_cl = [Kp, Ki]
den_cl = [1, 1, 1 + Kp, Ki]
label = f'Ki={Ki}'
sys_cl = signal.TransferFunction(num_cl, den_cl)
t_out, y_out = signal.step(sys_cl, T=t)
plt.plot(t_out, y_out, linewidth=1.5, label=label)
plt.axhline(y=1, color='k', linestyle='--', alpha=0.3, label='目標値')
plt.xlabel('Time [s]', fontsize=12)
plt.ylabel('Output', fontsize=12)
plt.title(f'PI制御: Kiの影響 (Kp={Kp})', fontsize=14)
plt.legend(fontsize=9)
plt.grid(True, alpha=0.3)
plt.xlim([0, 30])
plt.tight_layout()
plt.show()
このグラフから、PI制御の効果が明確に読み取れます。
-
定常偏差の除去: $K_i > 0$ のすべてのケースで、出力が最終的に目標値1.0に収束しています。$K_i = 0$(P制御のみ)では定常値が $2/3 \approx 0.667$ に留まっていたことと比較すると、I制御の効果は歴然です。
-
$K_i$ とオーバーシュートのトレードオフ: $K_i$ を大きくすると、定常偏差の除去は速くなりますが、オーバーシュートも大きくなります。$K_i = 5.0$ では、目標値を大きく超えて振動的な挙動を示しています。これは、積分項の蓄積が過剰になり、修正が「行き過ぎる」ためです。
-
整定時間への影響: $K_i$ が大きすぎると振動が長引き、整定時間が伸びる傾向が見られます。適度な $K_i$(例えば $K_i = 1.0$)では、比較的小さなオーバーシュートで定常偏差を除去できています。
PID制御: 微分項によるオーバーシュートの抑制
最後に、PI制御にD項を加えたPID制御の効果を確認します。$K_p = 5.0$, $K_i = 2.0$ に固定し、$K_d$ を変化させます。
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# 制御対象: G(s) = 1 / (s^2 + s + 1)
# PID制御: C(s) = Kp + Ki/s + Kd*s = (Kd*s^2 + Kp*s + Ki) / s
# 閉ループ: (Kd*s^2 + Kp*s + Ki) / (s^3 + (1+Kd)*s^2 + (1+Kp)*s + Ki)
Kp = 5.0
Ki = 2.0
t = np.linspace(0, 30, 3000)
plt.figure(figsize=(10, 6))
for Kd in [0, 0.5, 1.0, 2.0, 5.0]:
num_cl = [Kd, Kp, Ki]
den_cl = [1, 1 + Kd, 1 + Kp, Ki]
sys_cl = signal.TransferFunction(num_cl, den_cl)
t_out, y_out = signal.step(sys_cl, T=t)
label = f'Kd={Kd}' + (' (PI制御のみ)' if Kd == 0 else '')
plt.plot(t_out, y_out, linewidth=1.5, label=label)
plt.axhline(y=1, color='k', linestyle='--', alpha=0.3, label='目標値')
plt.xlabel('Time [s]', fontsize=12)
plt.ylabel('Output', fontsize=12)
plt.title(f'PID制御: Kdの影響 (Kp={Kp}, Ki={Ki})', fontsize=14)
plt.legend(fontsize=9)
plt.grid(True, alpha=0.3)
plt.xlim([0, 30])
plt.tight_layout()
plt.show()
このグラフからは、D制御の効果が鮮明に見て取れます。
-
オーバーシュートの抑制: $K_d = 0$(PI制御のみ)では大きなオーバーシュートと振動が見られますが、$K_d$ を増やすにつれてオーバーシュートが小さくなっています。これは、D項が「出力が急速に目標値に近づいている」ことを検知し、操作量を先回りして減らすためです。
-
振動の減衰: D項は閉ループ系の減衰比を増加させる効果があるため、振動がより速く収束するようになっています。特に $K_d = 2.0$ あたりでは、ほとんどオーバーシュートのない滑らかな応答が得られています。
-
$K_d$ が大きすぎる場合の弊害: $K_d = 5.0$ では応答が過度に抑制され、立ち上がりが遅くなる傾向が見られます。D項が強すぎると「慎重すぎる」制御になってしまうのです。また、実際のシステムではD項が大きいほどノイズの影響を受けやすくなるため、$K_d$ の値は適度に設定する必要があります。
P / PI / PID の総合比較
最後に、P制御・PI制御・PID制御をそれぞれ適切なパラメータで比較し、3つの制御方式の違いを一目で把握できるようにしましょう。
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
t = np.linspace(0, 30, 3000)
configs = [
# (分子係数, 分母係数, ラベル, 色)
([2.0], [1, 1, 1 + 2.0],
'P制御 (Kp=2)', 'tab:blue'),
([2.0, 1.0], [1, 1, 3.0, 1.0],
'PI制御 (Kp=2, Ki=1)', 'tab:orange'),
([1.0, 5.0, 2.0], [1, 2.0, 6.0, 2.0],
'PID制御 (Kp=5, Ki=2, Kd=1)', 'tab:green'),
]
plt.figure(figsize=(10, 6))
for num_cl, den_cl, label, color in configs:
sys_cl = signal.TransferFunction(num_cl, den_cl)
t_out, y_out = signal.step(sys_cl, T=t)
plt.plot(t_out, y_out, linewidth=2, label=label, color=color)
plt.axhline(y=1, color='k', linestyle='--', alpha=0.3, label='目標値')
plt.xlabel('Time [s]', fontsize=12)
plt.ylabel('Output', fontsize=12)
plt.title('P制御 vs PI制御 vs PID制御', fontsize=14)
plt.legend(fontsize=10)
plt.grid(True, alpha=0.3)
plt.xlim([0, 30])
plt.tight_layout()
plt.show()
この総合比較グラフは、PID制御の本質を端的に表しています。
-
P制御(青): 応答は速いものの、目標値1.0に到達できず、定常偏差が残っています。制御としては不十分です。
-
PI制御(橙): I項の追加により定常偏差は解消され、最終的に出力は目標値1.0に収束しています。しかし、オーバーシュートが大きく、振動的な過渡応答が見られます。
-
PID制御(緑): D項がオーバーシュートを抑制し、PI制御と同じく定常偏差ゼロを達成しつつ、より滑らかで速い応答を実現しています。3つの中で最もバランスの良い応答と言えます。
このように、P・I・Dの3要素はそれぞれ異なる役割を担い、それらを適切に組み合わせることで、「素早く、正確に、安定して」目標値に追従する制御系を実現できるのです。
ジーグラー・ニコルス法の適用例
最後に、ジーグラー・ニコルス法を使ってPIDパラメータを算出する例をPythonで実装してみましょう。ここでは、P制御のみでゲインを上げていき、限界ゲインと限界周期を数値的に求めます。
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# 制御対象: G(s) = 1 / (s^2 + s + 1)
num_plant = [1]
den_plant = [1, 1, 1]
# ジーグラー・ニコルス法: 限界ゲインKuと限界周期Tuを求める
# P制御の閉ループ系: Kp / (s^2 + s + 1 + Kp)
# 特性方程式: s^2 + s + (1+Kp) = 0
# ラウスの安定判別より、持続振動条件は s = jw を代入して:
# (jw)^2 + jw + (1+Kp) = 0
# -(w^2) + (1+Kp) + jw = 0
# 実部: -(w^2) + (1+Kp) = 0 => w^2 = 1+Kp
# 虚部: w = 0 (矛盾) => この2次系では持続振動しない
#
# より現実的な制御対象として3次系を使用:
# G(s) = 1 / (s+1)^3 = 1 / (s^3 + 3s^2 + 3s + 1)
num_plant_3rd = [1]
den_plant_3rd = [1, 3, 3, 1]
t = np.linspace(0, 50, 5000)
# ゲインを変化させて応答を観察
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
Kp_values = [1.0, 4.0, 7.0, 8.0]
for ax, Kp in zip(axes.flat, Kp_values):
# 閉ループ: Kp / (s^3 + 3s^2 + 3s + 1 + Kp)
num_cl = [Kp]
den_cl = [1, 3, 3, 1 + Kp]
sys_cl = signal.TransferFunction(num_cl, den_cl)
t_out, y_out = signal.step(sys_cl, T=t)
ax.plot(t_out, y_out, linewidth=1.5, color='tab:blue')
ax.axhline(y=1, color='k', linestyle='--', alpha=0.3)
ax.set_xlabel('Time [s]', fontsize=11)
ax.set_ylabel('Output', fontsize=11)
ax.set_title(f'Kp = {Kp}', fontsize=13)
ax.grid(True, alpha=0.3)
ax.set_xlim([0, 50])
ax.set_ylim([-0.5, 2.5])
plt.suptitle('ジーグラー・ニコルス法: Kpを増加させて限界ゲインを探索',
fontsize=14, y=1.02)
plt.tight_layout()
plt.show()
# ラウスの安定判別による限界ゲインの理論値
# 特性方程式: s^3 + 3s^2 + 3s + (1+Kp) = 0
# ラウス表:
# s^3: 1, 3
# s^2: 3, (1+Kp)
# s^1: (9-(1+Kp))/3 = (8-Kp)/3
# s^0: (1+Kp)
# 持続振動条件: (8-Kp)/3 = 0 => Kp = 8 (= Ku)
Ku = 8.0
# 限界周期の計算: s = jw を代入
# (jw)^3 + 3(jw)^2 + 3(jw) + (1+Ku) = 0
# -jw^3 - 3w^2 + 3jw + 9 = 0
# 実部: -3w^2 + 9 = 0 => w = sqrt(3)
# Tu = 2*pi / w
w_u = np.sqrt(3)
Tu = 2 * np.pi / w_u
print(f"限界ゲイン Ku = {Ku}")
print(f"限界周波数 wu = {w_u:.4f} rad/s")
print(f"限界周期 Tu = {Tu:.4f} s")
# ジーグラー・ニコルス法のパラメータ算出
print("\n--- ジーグラー・ニコルス法によるパラメータ ---")
# P制御
Kp_P = 0.5 * Ku
print(f"P制御: Kp = {Kp_P:.2f}")
# PI制御
Kp_PI = 0.45 * Ku
Ti_PI = Tu / 1.2
Ki_PI = Kp_PI / Ti_PI
print(f"PI制御: Kp = {Kp_PI:.2f}, Ti = {Ti_PI:.4f}, Ki = {Ki_PI:.4f}")
# PID制御
Kp_PID = 0.6 * Ku
Ti_PID = Tu / 2
Td_PID = Tu / 8
Ki_PID = Kp_PID / Ti_PID
Kd_PID = Kp_PID * Td_PID
print(f"PID制御: Kp = {Kp_PID:.2f}, Ti = {Ti_PID:.4f}, Td = {Td_PID:.4f}")
print(f" Ki = {Ki_PID:.4f}, Kd = {Kd_PID:.4f}")
このシミュレーションでは、3次系 $G(s) = 1/(s+1)^3$ に対してP制御のゲインを上げていき、限界ゲインを探索しています。上のグラフから、以下のことが読み取れます。
-
$K_p = 1.0$: 応答はゆっくりと定常値に収束しており、十分に安定しています。しかし、定常偏差は $1/(1+1) = 0.5$ と大きい状態です。
-
$K_p = 4.0$: 応答が速くなり、オーバーシュートも見え始めています。まだ安定ですが、振動的な成分が出てきています。
-
$K_p = 7.0$: 振動が顕著になり、減衰も遅くなっています。安定の境界に近づいていることがわかります。
-
$K_p = 8.0$: これが限界ゲイン $K_u = 8$ です。ラウスの安定判別から理論的に求められるように、この値では出力が一定振幅で振動し続けます(持続振動)。限界周期は $T_u = 2\pi/\sqrt{3} \approx 3.63$ 秒です。
ラウス・フルヴィッツの安定判別を適用すると、特性方程式 $s^3 + 3s^2 + 3s + (1 + K_p) = 0$ のラウス表から、持続振動条件は $K_p = 8$ と厳密に求まります。
ジーグラー・ニコルス法の結果を検証する
ジーグラー・ニコルス法で得られたパラメータを使って、P/PI/PID制御の応答を比較してみましょう。
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# 制御対象: G(s) = 1/(s+1)^3
# ジーグラー・ニコルス法のパラメータ
Ku = 8.0
Tu = 2 * np.pi / np.sqrt(3)
# P制御パラメータ
Kp_P = 0.5 * Ku # = 4.0
# PI制御パラメータ
Kp_PI = 0.45 * Ku # = 3.6
Ti_PI = Tu / 1.2
Ki_PI = Kp_PI / Ti_PI
# PID制御パラメータ
Kp_PID = 0.6 * Ku # = 4.8
Ti_PID = Tu / 2
Td_PID = Tu / 8
Ki_PID = Kp_PID / Ti_PID
Kd_PID = Kp_PID * Td_PID
t = np.linspace(0, 30, 3000)
plt.figure(figsize=(10, 6))
# P制御: Kp / (s^3 + 3s^2 + 3s + 1 + Kp)
num_P = [Kp_P]
den_P = [1, 3, 3, 1 + Kp_P]
sys_P = signal.TransferFunction(num_P, den_P)
t_out, y_P = signal.step(sys_P, T=t)
plt.plot(t_out, y_P, linewidth=2, label=f'P (Kp={Kp_P:.1f})', color='tab:blue')
# PI制御: (Kp*s + Ki) / (s^4 + 3s^3 + 3s^2 + (1+Kp)*s + Ki)
num_PI = [Kp_PI, Ki_PI]
den_PI = [1, 3, 3, 1 + Kp_PI, Ki_PI]
sys_PI = signal.TransferFunction(num_PI, den_PI)
t_out, y_PI = signal.step(sys_PI, T=t)
plt.plot(t_out, y_PI, linewidth=2,
label=f'PI (Kp={Kp_PI:.1f}, Ki={Ki_PI:.2f})', color='tab:orange')
# PID制御: (Kd*s^2 + Kp*s + Ki) / (s^4 + (3+Kd)*s^3 + (3+Kp)*s^2 + (1+...)*s + Ki)
# C(s)*G(s) = (Kd*s^2 + Kp*s + Ki) / (s*(s+1)^3)
# 閉ループ: (Kd*s^2+Kp*s+Ki) / (s^4+3s^3+3s^2+s + Kd*s^2+Kp*s+Ki)
num_PID = [Kd_PID, Kp_PID, Ki_PID]
den_PID = [1, 3, 3 + Kd_PID, 1 + Kp_PID, Ki_PID]
sys_PID = signal.TransferFunction(num_PID, den_PID)
t_out, y_PID = signal.step(sys_PID, T=t)
plt.plot(t_out, y_PID, linewidth=2,
label=f'PID (Kp={Kp_PID:.1f}, Ki={Ki_PID:.2f}, Kd={Kd_PID:.2f})',
color='tab:green')
plt.axhline(y=1, color='k', linestyle='--', alpha=0.3, label='目標値')
plt.xlabel('Time [s]', fontsize=12)
plt.ylabel('Output', fontsize=12)
plt.title('ジーグラー・ニコルス法で設計したP/PI/PID制御の比較', fontsize=14)
plt.legend(fontsize=9)
plt.grid(True, alpha=0.3)
plt.xlim([0, 30])
plt.tight_layout()
plt.show()
# 性能指標の計算
print("--- 性能指標 ---")
for name, y in [('P', y_P), ('PI', y_PI), ('PID', y_PID)]:
# オーバーシュート
overshoot = (np.max(y) - 1.0) * 100 if np.max(y) > 1.0 else 0
# 整定時間 (±5%範囲)
settled = np.where(np.abs(y - y[-1]) < 0.05 * y[-1])[0]
if len(settled) > 0:
# 最後に範囲外に出た時刻以降
outside = np.where(np.abs(y - y[-1]) >= 0.05 * y[-1])[0]
settling_time = t[outside[-1]] if len(outside) > 0 else 0
else:
settling_time = float('inf')
# 定常偏差
ss_error = abs(1.0 - y[-1])
print(f"{name:>3s}: オーバーシュート={overshoot:.1f}%, "
f"整定時間≈{settling_time:.1f}s, 定常偏差={ss_error:.4f}")
このシミュレーション結果から、ジーグラー・ニコルス法で設計した各制御器の特性が明確に比較できます。
-
P制御: ジーグラー・ニコルス法では $K_p = 0.5 K_u = 4.0$ と設定されています。限界ゲインの半分なので、十分な安定余裕があり、適度な応答速度を示しています。しかし、定常偏差は依然として残っています。
-
PI制御: I項の追加により定常偏差が除去されています。ただし、ジーグラー・ニコルス法のPI設計はオーバーシュートがやや大きい傾向があります。これは、この手法が「ある程度の振動を許容して応答速度を確保する」設計哲学に基づいているためです。
-
PID制御: D項の追加により、PI制御と比べてオーバーシュートが抑制され、整定時間も短くなっています。3つの制御方式の中で最もバランスの良い応答を示しており、「速く、正確に、安定して」という3つの目標を同時に達成しています。
性能指標(オーバーシュート、整定時間、定常偏差)の数値を確認すると、P→PI→PIDと制御要素を追加するごとに、制御性能が段階的に改善されていく様子が定量的にわかります。
実用上の補足: PID制御の発展的トピック
ここまでで基本的なPID制御の理論と実装を扱いましたが、実際のシステムにPID制御を適用する際には、さらにいくつかの実用的なテクニックを知っておくと役立ちます。
微分先行型PID制御
これまで説明した標準的なPID制御では、目標値 $r(t)$ が急変(ステップ状に変化)すると、偏差 $e(t) = r(t) – y(t)$ も急変し、D項が非常に大きな値(理論上は無限大)を出力してしまいます。これを 微分キック(derivative kick) と呼びます。
この問題を回避するために、偏差 $e(t)$ ではなく出力 $y(t)$ のみを微分する 微分先行型 が実用でよく使われます。
$$ u(t) = K_p \, e(t) + K_i \int_0^t e(\tau) \, d\tau – K_d \frac{dy(t)}{dt} $$
出力 $y(t)$ はステップ状に変化しないため、D項の出力が急激に変化することはありません。符号がマイナスになっていることに注意してください。$e = r – y$ の微分は $de/dt = dr/dt – dy/dt$ ですが、目標値が一定なら $dr/dt = 0$ なので、$de/dt = -dy/dt$ となります。
離散時間でのPID制御
デジタルコンピュータでPID制御を実装する場合、連続時間の積分と微分を離散近似に置き換える必要があります。サンプリング周期を $\Delta t$ とすると、
- 積分項: 台形法や矩形法で近似 $\int_0^t e \, d\tau \approx \sum_{k=0}^{n} e_k \, \Delta t$
- 微分項: 後退差分で近似 $de/dt \approx (e_n – e_{n-1}) / \Delta t$
離散PID制御のアルゴリズムは次のようになります。
$$ u_n = K_p \, e_n + K_i \, \Delta t \sum_{k=0}^{n} e_k + K_d \frac{e_n – e_{n-1}}{\Delta t} $$
サンプリング周期 $\Delta t$ は、制御対象の応答速度に比べて十分短くする(制御対象の時定数の1/10以下が目安)必要があります。
PID制御の限界と発展
PID制御は汎用性の高い優れた手法ですが、以下のような場面では限界があり、より高度な制御手法が必要になることもあります。
- 多入力多出力(MIMO)系: PID制御は基本的に1入力1出力(SISO)系向けです。MIMO系には現代制御理論に基づく状態フィードバック制御が適しています。
- 非線形性が強い系: PID制御は線形システムを前提としているため、強い非線形性を持つシステムでは性能が低下します。
- 外乱やモデル変動が大きい系: ロバスト制御やモデル予測制御(MPC)などの高度な手法が有効です。
これらの発展的な手法に興味がある方は、周波数応答とボード線図やゲイン余裕と位相余裕を通じて周波数領域の設計法を学び、さらにロバスト制御や最適制御へ進むことをお勧めします。
まとめ
本記事では、PID制御の理論を直感的な理解から数学的定義、そしてPythonシミュレーションによる検証まで、一貫して解説しました。
- PID制御の基本構造: 操作量は偏差の比例項・積分項・微分項の和 $u(t) = K_p e + K_i \int e \, dt + K_d \dot{e}$ で表される
- P制御(比例制御): 偏差に比例した操作量を出力し、応答速度を上げる。ただし、定常偏差が原理的に残る
- I制御(積分制御): 偏差の時間蓄積に基づき、定常偏差を除去する。ただし、オーバーシュートの増大やワインドアップに注意が必要
- D制御(微分制御): 偏差の変化速度を見て先手を打ち、オーバーシュートの抑制と振動の減衰に貢献する。ノイズに敏感なため、不完全微分を使うのが実用的
- ジーグラー・ニコルス法: 限界ゲイン $K_u$ と限界周期 $T_u$ を実験的に求め、P/PI/PIDパラメータを系統的に決定する古典的手法
- Pythonシミュレーション: P→PI→PIDと制御要素を追加するごとに、応答性能(定常偏差の除去、オーバーシュートの抑制、整定時間の短縮)が段階的に改善されることを確認した
PID制御は、そのシンプルな構造にもかかわらず、多くの制御問題に対して十分な性能を発揮する強力なツールです。本記事で学んだ各パラメータの効果とチューニング法を理解しておけば、実際のシステム設計において適切なPID制御器を設計するための確かな基盤となるでしょう。
次のステップとして、以下の記事も参考にしてください。
- フィードバック制御の基本 — PID制御の土台となるフィードバック制御の概念を復習
- 周波数応答とボード線図 — PID制御器の周波数特性を理解し、より高度なチューニングへ
- ゲイン余裕と位相余裕 — ジーグラー・ニコルス法の背景にある安定余裕の概念を深掘り
- 根軌跡法 — PIDパラメータが閉ループ極に与える影響を可視化
- Pythonで制御系シミュレーション — python-controlライブラリを使ったより本格的なシミュレーション