不静定問題の解法を基礎から徹底解説

橋を設計するとき、支持点を2つにするか3つにするか — この判断は構造の安全性と解析の難しさを大きく左右します。支持点が増えるほど構造は冗長になり安全性は高まりますが、力のつり合い式だけでは内力や反力を求められなくなります。この「つり合い式だけでは解けない問題」が不静定問題(statically indeterminate problem) です。

不静定構造は身の回りに溢れています。航空機の翼桁は複数のリベットで結合された不静定構造ですし、鉄筋コンクリートの連続梁も典型的な不静定はりです。温度変化で生じる熱応力の問題も、拘束条件が加わることで不静定になります。不静定問題の解法を身につけることは、実際の構造設計に直結する重要なスキルです。

本記事の内容

  • 静定と不静定の定義・判別法
  • 不静定次数の数え方
  • 力の方法(適合条件法)の体系的手順
  • 変位の方法(剛性法)の基本的な考え方
  • 棒の不静定問題(段付き棒・温度変化)
  • はりの不静定問題(両端固定梁・連続梁)
  • Pythonによる不静定問題の数値解析と可視化

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

静定と不静定の定義

つり合い方程式の限界

材料力学で最初に学ぶ問題は、力のつり合い式だけで全ての未知力を決定できる静定問題(statically determinate problem) です。たとえば、単純支持梁(両端がピン支持とローラー支持)に集中荷重が作用する場合、鉛直方向の力のつり合いとモーメントのつり合いの2式から、2つの反力を一意に決定できます。

しかし、構造の支持条件を変えて両端を固定端にするとどうなるでしょうか。固定端は鉛直反力に加えてモーメント反力も持つため、未知反力は合計4つになります。一方、2次元のはりで使えるつり合い方程式は:

$$ \sum F_x = 0, \quad \sum F_y = 0, \quad \sum M = 0 $$

の最大3本です。未知数4個に対して方程式3本 — 方程式が足りません。これが不静定問題の本質です。

静定問題の定義

構造物のつり合い方程式だけで全ての未知反力および内力が一意に決定できる問題を静定問題と呼びます。

静定問題の特徴:

  • 未知力の数 = つり合い方程式の数
  • 材料特性(ヤング率など)に依存せず反力が決まる
  • 部材の剛性を変えても反力は変化しない

不静定問題の定義

つり合い方程式だけでは未知力を一意に決定できない問題を不静定問題と呼びます。不静定問題を解くには、つり合い方程式に加えて変形の適合条件(compatibility condition)構成則(フックの法則) が必要です。

不静定問題の特徴:

  • 未知力の数 > つり合い方程式の数
  • 材料特性や断面寸法が反力の大きさに影響する
  • 剛性の高い部材がより多くの荷重を分担する

つまり、不静定問題では「力のつり合い」「変形の適合条件」「材料の構成則(フックの法則)」の3つを同時に満たす解を求める必要があります。この3本柱が不静定問題解法の核心です。

では、つり合い方程式に対してどれだけ未知力が余っているかを定量的に測るにはどうすればよいでしょうか。その指標が「不静定次数」です。

不静定次数

不静定次数の定義

不静定次数(degree of static indeterminacy) とは、つり合い方程式の数に対して未知力が何個多いかを表す整数です。不静定次数を $n$ とすると:

$$ \boxed{n = (\text{未知反力の数}) – (\text{独立なつり合い方程式の数})} $$

$n = 0$ のとき静定、$n > 0$ のとき $n$ 次不静定、$n < 0$ のとき不安定(機構)です。

不静定次数は「追加で何本の方程式が必要か」を教えてくれます。$n$ 次の不静定問題を解くには、$n$ 本の適合条件式を立てる必要があります。

棒(軸力問題)の不静定次数

1次元の棒の軸力問題では、つり合い方程式は通常1本(力の方向が1方向のみ)です。

例1: 両端固定の棒

両端が壁に固定された棒には、左端と右端にそれぞれ反力(未知数2個)が作用します。つり合い方程式は $\sum F_x = 0$ の1本なので:

$$ n = 2 – 1 = 1 \quad (\text{1次不静定}) $$

例2: 3つの壁で支持された棒

3つの反力に対してつり合い方程式は1本なので:

$$ n = 3 – 1 = 2 \quad (\text{2次不静定}) $$

はり(曲げ問題)の不静定次数

2次元のはりでは、独立なつり合い方程式は $\sum F_y = 0$ と $\sum M = 0$ の2本です(水平力がない場合、$\sum F_x = 0$ は自明に成立)。

各支持条件が提供する反力の数:

支持条件 反力の数 内訳
ローラー支持 1 鉛直反力
ピン支持 2 鉛直反力 + 水平反力
固定端 3 鉛直反力 + 水平反力 + モーメント

例3: 両端固定梁

固定端2つで反力6個(ただし水平力を無視すれば鉛直反力2 + モーメント反力2 = 4個)、つり合い方程式2本:

$$ n = 4 – 2 = 2 \quad (\text{2次不静定}) $$

例4: 連続梁(3スパン)

たとえば4つのローラー支持点を持つ3スパン連続梁では、反力4個、つり合い方程式2本:

$$ n = 4 – 2 = 2 \quad (\text{2次不静定}) $$

トラスの不静定次数

平面トラスの不静定次数は、部材数 $m$、節点数 $j$、反力数 $r$ を用いて:

$$ \boxed{n = m + r – 2j} $$

で計算されます。$2j$ は各節点の $x, y$ 方向のつり合い式の総数です。この式は、部材の軸力($m$ 個)と反力($r$ 個)を合わせた未知力の総数 $m + r$ から、つり合い方程式の総数 $2j$ を引いたものです。

不静定次数がわかれば、あとはその数だけ適合条件を立てればよい — この見通しが解法の出発点です。次に、不静定問題を解く具体的な手順を2つの方法で学びましょう。

力の方法(適合条件法)

力の方法とは

力の方法(force method)、別名適合条件法(compatibility method) は、不静定問題を解く最も古典的な手法です。余分な反力(冗長力)を未知数として扱い、変形の適合条件から冗長力を決定します。

この方法の着想はシンプルです。「まず余分な拘束を取り除いて静定構造にし、取り除いた拘束の位置で変形が本来の条件(たとえば変位ゼロ)を満たすように、冗長力の大きさを調整する」という考え方です。

体系的手順

力の方法は以下の手順で進めます。

ステップ1: 不静定次数の確認

未知反力の数からつり合い方程式の数を引き、不静定次数 $n$ を求めます。

ステップ2: 冗長力の選択と基本系の設定

$n$ 個の反力を冗長力(redundant force) として選びます。冗長力に対応する拘束を取り除いた構造を基本系(released structure / primary structure) と呼びます。基本系は静定構造でなければなりません。

冗長力の選び方は一意ではありませんが、基本系が不安定にならないように注意します。

ステップ3: 適合条件の設定

冗長力を取り除いた位置での変形条件を式にします。たとえば、あるローラー支持を取り除いた場合、その点での本来の鉛直変位はゼロなので:

$$ \delta_{\text{total}} = 0 $$

が適合条件になります。

ステップ4: 重ね合わせによる変形の表現

基本系に外力のみを作用させたときの変形 $\delta_0$ と、冗長力 $X$ のみを作用させたときの変形 $\delta_X$ を重ね合わせます:

$$ \delta_{\text{total}} = \delta_0 + \delta_X = 0 $$

弾性体では重ね合わせの原理が成り立つため、冗長力 $X$ による変形は $X$ に比例します:

$$ \delta_X = f \cdot X $$

ここで $f$ は柔軟性係数(flexibility coefficient) と呼ばれ、冗長力方向に単位力を加えたときの変位です。

ステップ5: 冗長力の決定

適合条件を解いて冗長力 $X$ を求めます:

$$ \delta_0 + f \cdot X = 0 \implies \boxed{X = -\frac{\delta_0}{f}} $$

ステップ6: 全反力と内力の計算

冗長力が求まれば、つり合い方程式から残りの反力と内力を決定できます。

1次不静定の具体的な適用

両端固定の棒(長さ $L$、断面積 $A$、ヤング率 $E$)の中間点 $x = a$ に集中荷重 $P$ が作用する場合を考えましょう。

左端の反力を $R_A$、右端の反力を $R_B$ とします。つり合い方程式は:

$$ R_A + R_B = P \tag{1} $$

未知数2個に対して方程式1本なので、1次不静定です。$R_B$ を冗長力に選び、右端の拘束を取り除いた基本系(左端固定の片持ち棒)を考えます。

適合条件は「右端の変位がゼロ」:

$$ \delta_B = 0 \tag{2} $$

外力 $P$ のみによる右端の伸び(左端からの全体伸び)と、冗長力 $R_B$(左向き)による縮みを重ね合わせます。

$P$ による伸び: 左端から距離 $a$ の点に力 $P$ が右向きに作用するとき、区間 $[0, a]$ の軸力は $P$、区間 $[a, L]$ の軸力は $0$ です。全体の伸びは:

$$ \delta_0 = \frac{Pa}{AE} $$

$R_B$ による縮み: 右端に $R_B$(左向き)が作用するとき、棒全体に軸力 $-R_B$ が作用し:

$$ \delta_{R_B} = -\frac{R_B L}{AE} $$

適合条件 $\delta_0 + \delta_{R_B} = 0$ に代入すると:

$$ \frac{Pa}{AE} – \frac{R_B L}{AE} = 0 $$

$AE$ を消去して:

$$ R_B = \frac{Pa}{L} $$

つり合い方程式 (1) から:

$$ R_A = P – R_B = P\left(1 – \frac{a}{L}\right) = \frac{P(L-a)}{L} = \frac{Pb}{L} $$

ここで $b = L – a$ としました。荷重が中央にある場合($a = L/2$)、$R_A = R_B = P/2$ となり、直感にも合致します。

この例で重要なのは、反力の大きさが断面積 $A$ やヤング率 $E$ に依存しなかった点です。これは棒が一様断面・一様材料だったためです。段付き棒や異種材料の場合は、軸力と伸びの公式を区間ごとに適用するため、反力は $A$ や $E$ に依存します。

力の方法は「冗長力を選んで、変位の条件から決める」という直感的に分かりやすい方法ですが、不静定次数が高くなると冗長力の組み合わせが増えて煩雑になります。そこで、もう一つのアプローチ — 変位を未知数とする方法を見てみましょう。

変位の方法(剛性法)

変位の方法とは

変位の方法(displacement method) は、節点の変位や回転角を未知数として、つり合い方程式を変位で表現する方法です。コンピュータによる構造解析(有限要素法)の基盤となる手法であり、剛性法(stiffness method) とも呼ばれます。

力の方法が「余分な力を仮定して変形条件で決める」のに対し、変位の方法は「変位を仮定して力の条件で決める」という逆のアプローチを取ります。

基本的な考え方

変位の方法では、各部材の力-変位関係(剛性関係) を先に用意します。

棒部材(軸力)の場合:

$$ P = \frac{AE}{L} \delta = k \delta $$

ここで $k = AE/L$ は軸剛性です。この式は、ばねの法則 $F = kx$ と同じ形をしています。フックの法則から、弾性体の各部材はばねとしてモデル化できるのです。

はり部材(曲げ)の場合、端部のせん断力とモーメントを端部の変位と回転角で表す部材剛性方程式を使います:

$$ \begin{pmatrix} V_1 \\ M_1 \\ V_2 \\ M_2 \end{pmatrix} = \frac{EI}{L^3} \begin{pmatrix} 12 & 6L & -12 & 6L \\ 6L & 4L^2 & -6L & 2L^2 \\ -12 & -6L & 12 & -6L \\ 6L & 2L^2 & -6L & 4L^2 \end{pmatrix} \begin{pmatrix} v_1 \\ \theta_1 \\ v_2 \\ \theta_2 \end{pmatrix} $$

この行列を要素剛性行列と呼びます。$EI$ は曲げ剛性です。

体系的手順

ステップ1: 未知変位の特定

支持条件から拘束されていない変位自由度を洗い出します。

ステップ2: 部材剛性行列の組み立て

各部材の剛性行列を計算します。

ステップ3: 全体剛性行列の組み立て

各部材の剛性行列を全体座標系に変換し、重ね合わせて全体剛性方程式を構成します:

$$ \bm{K} \bm{u} = \bm{f} $$

ここで $\bm{K}$ は全体剛性行列、$\bm{u}$ は未知変位ベクトル、$\bm{f}$ は外力ベクトルです。

ステップ4: 境界条件の適用と連立方程式の求解

既知の変位(拘束条件)を適用して方程式を縮小し、連立方程式を解いて未知変位を求めます。

ステップ5: 内力と反力の計算

求まった変位から、各部材の内力と支持点の反力を計算します。

力の方法との比較

特徴 力の方法 変位の方法
未知数 冗長力 節点変位
追加方程式 適合条件 つり合い方程式
手計算の容易さ 低次不静定で有利 不静定次数に依存しない
コンピュータ実装 困難 容易(有限要素法)
不静定次数との関係 不静定次数 = 未知力の数 運動自由度 = 未知数の数

手計算では不静定次数が低い場合に力の方法が便利ですが、大規模構造の解析では変位の方法(有限要素法)が圧倒的に有利です。

ここまでで2つの解法の枠組みを学びました。次に、これらの方法を具体的な問題に適用していきましょう。まずは最もシンプルな棒の不静定問題から始めます。

棒の不静定問題

段付き棒

段付き棒とは、断面積やヤング率が途中で不連続に変化する棒のことです。段付き棒が両端固定されて外力を受ける場合、各区間の軸力を求めるには力の方法を使います。

問題設定: 両端が固定された段付き棒を考えます。左側の区間(長さ $L_1$、断面積 $A_1$、ヤング率 $E_1$)と右側の区間(長さ $L_2$、断面積 $A_2$、ヤング率 $E_2$)の境界に、右向きの荷重 $P$ が作用します。

解法:

つり合い方程式:

$$ R_A + R_B = P \tag{3} $$

ここで $R_A$ は左壁の反力(右向き正)、$R_B$ は右壁の反力(左向き正)です。

適合条件(両端固定なので全体の伸びがゼロ):

$$ \delta_{\text{total}} = \delta_1 + \delta_2 = 0 \tag{4} $$

各区間の変形を軸力と伸びの関係から求めます。区間1の軸力は $P – R_B$($= R_A$)、区間2の軸力は $-R_B$(圧縮)です:

$$ \delta_1 = \frac{(P – R_B) L_1}{A_1 E_1}, \quad \delta_2 = \frac{(-R_B) L_2}{A_2 E_2} $$

適合条件 (4) に代入します:

$$ \frac{(P – R_B) L_1}{A_1 E_1} + \frac{(-R_B) L_2}{A_2 E_2} = 0 $$

$R_B$ について解くと:

$$ \frac{P L_1}{A_1 E_1} = R_B \left(\frac{L_1}{A_1 E_1} + \frac{L_2}{A_2 E_2}\right) $$

$$ \boxed{R_B = \frac{P \cdot \dfrac{L_1}{A_1 E_1}}{\dfrac{L_1}{A_1 E_1} + \dfrac{L_2}{A_2 E_2}} = \frac{P \cdot k_2}{k_1 + k_2}} $$

ここで $k_i = A_i E_i / L_i$ は各区間の軸剛性です。最後の表現から、荷重が剛性に比例して分配されることがわかります。これは並列ばねの力の分配法則と同じです。

つり合い方程式 (3) から:

$$ R_A = P – R_B = \frac{P \cdot k_1}{k_1 + k_2} $$

一様断面($A_1 E_1 = A_2 E_2$, $L_1 = L_2 = L/2$)の場合、$k_1 = k_2$ なので $R_A = R_B = P/2$ に帰着し、荷重が均等に分配されます。

温度変化を含む不静定問題

温度変化が加わる場合、自由膨張分を適合条件に組み込む必要があります。熱応力の記事で学んだように、拘束がなければ温度変化は応力を生じませんが、拘束があると不静定問題になります。

問題設定: 両端固定の段付き棒(区間1: $L_1, A_1, E_1, \alpha_1$、区間2: $L_2, A_2, E_2, \alpha_2$)に、一様な温度変化 $\Delta T$ が与えられる場合。

適合条件(全体の伸びがゼロ):

$$ \delta_{\text{total}} = (\delta_{T1} + \delta_{P1}) + (\delta_{T2} + \delta_{P2}) = 0 $$

各項を展開します。$\delta_{Ti} = \alpha_i \Delta T L_i$ は自由膨張、$\delta_{Pi}$ は内力による弾性変形です。両端固定なので全区間を通じて軸力は一定値 $N$ です(外力がないため):

$$ \alpha_1 \Delta T L_1 + \frac{N L_1}{A_1 E_1} + \alpha_2 \Delta T L_2 + \frac{N L_2}{A_2 E_2} = 0 $$

$N$ について整理すると:

$$ N \left(\frac{L_1}{A_1 E_1} + \frac{L_2}{A_2 E_2}\right) = -(\alpha_1 L_1 + \alpha_2 L_2)\Delta T $$

$$ \boxed{N = -\frac{(\alpha_1 L_1 + \alpha_2 L_2)\Delta T}{\dfrac{L_1}{A_1 E_1} + \dfrac{L_2}{A_2 E_2}}} $$

$\Delta T > 0$(温度上昇)のとき $N < 0$ で圧縮力が生じます。各区間の応力は:

$$ \sigma_1 = \frac{N}{A_1}, \quad \sigma_2 = \frac{N}{A_2} $$

一様材料($\alpha_1 = \alpha_2 = \alpha$, $E_1 = E_2 = E$, $A_1 = A_2 = A$)の場合:

$$ N = -AE\alpha\Delta T, \quad \sigma = -E\alpha\Delta T $$

これは熱応力の基本公式に一致します。

ここまでで棒の不静定問題の基本的な解法を学びました。Pythonで数値的に解いてみましょう。

Pythonによる段付き棒の応力分布

段付き棒の不静定問題をPythonで解き、応力分布と変位分布を可視化します。以下のコードでは、3区間の段付き棒が両端固定され、区間境界に集中荷重が作用するケースを扱います。

import numpy as np
import matplotlib.pyplot as plt

# --- 段付き棒のパラメータ ---
# 3区間: [0, L1], [L1, L1+L2], [L1+L2, L1+L2+L3]
L = np.array([0.3, 0.4, 0.3])       # 各区間の長さ [m]
A = np.array([600e-6, 400e-6, 200e-6])  # 各区間の断面積 [m^2]
E = np.array([200e9, 200e9, 200e9])  # 各区間のヤング率 [Pa]

# 荷重: 区間1と2の境界に右向き80kN、区間2と3の境界に左向き30kN
P_ext = np.array([80e3, -30e3])  # 節点1, 節点2 に作用する外力 [N]

# --- 軸剛性の計算 ---
k = A * E / L  # 各区間の軸剛性 [N/m]

# --- 全体剛性行列の組み立て(変位の方法)---
# 内部節点: 節点1(区間1と2の境界)、節点2(区間2と3の境界)
# 全体剛性行列 K (2x2)
K = np.array([
    [k[0] + k[1], -k[1]],
    [-k[1], k[1] + k[2]]
])

# 連立方程式 K @ u = P_ext を解く
u = np.linalg.solve(K, P_ext)

print("=== 段付き棒の不静定解析 ===")
print(f"節点1の変位: {u[0]*1e6:.2f} μm")
print(f"節点2の変位: {u[1]*1e6:.2f} μm")

# --- 各区間の軸力と応力 ---
# 区間1: N1 = k[0] * u[0] (左壁から節点1への変位)
# 区間2: N2 = k[1] * (u[1] - u[0])
# 区間3: N3 = k[2] * (0 - u[1]) = -k[2] * u[1]
N = np.array([k[0]*u[0], k[1]*(u[1]-u[0]), -k[2]*u[1]])
sigma = N / A  # 各区間の応力 [Pa]

print(f"\n区間1: N = {N[0]/1e3:.2f} kN, σ = {sigma[0]/1e6:.2f} MPa")
print(f"区間2: N = {N[1]/1e3:.2f} kN, σ = {sigma[1]/1e6:.2f} MPa")
print(f"区間3: N = {N[2]/1e3:.2f} kN, σ = {sigma[2]/1e6:.2f} MPa")

# --- 反力 ---
R_A = -N[0]   # 左壁の反力(右向き正)
R_B = N[2]    # 右壁の反力(左向き正)
print(f"\n左壁の反力 R_A = {R_A/1e3:.2f} kN")
print(f"右壁の反力 R_B = {R_B/1e3:.2f} kN")
print(f"検算: R_A + 80 - 30 + R_B = {(R_A + 80e3 - 30e3 + R_B)/1e3:.4f} kN")

# --- 可視化 ---
fig, axes = plt.subplots(3, 1, figsize=(10, 10))
x_boundaries = np.cumsum(np.concatenate([[0], L]))

# (a) 応力分布
for i in range(3):
    axes[0].fill_between([x_boundaries[i], x_boundaries[i+1]],
                         sigma[i]/1e6, 0, alpha=0.3,
                         color='blue' if sigma[i] > 0 else 'red')
    axes[0].plot([x_boundaries[i], x_boundaries[i+1]],
                [sigma[i]/1e6, sigma[i]/1e6], 'k-', linewidth=2)
    axes[0].text((x_boundaries[i]+x_boundaries[i+1])/2, sigma[i]/1e6,
                f'{sigma[i]/1e6:.1f} MPa', ha='center', va='bottom' if sigma[i]>0 else 'top',
                fontsize=11, fontweight='bold')
axes[0].axhline(0, color='k', linewidth=0.5)
axes[0].set_ylabel('Stress [MPa]')
axes[0].set_title('(a) Stress Distribution')
axes[0].grid(True, alpha=0.3)

# (b) 軸力分布
for i in range(3):
    color = 'blue' if N[i] > 0 else 'red'
    axes[1].fill_between([x_boundaries[i], x_boundaries[i+1]],
                         N[i]/1e3, 0, alpha=0.3, color=color)
    axes[1].plot([x_boundaries[i], x_boundaries[i+1]],
                [N[i]/1e3, N[i]/1e3], 'k-', linewidth=2)
    axes[1].text((x_boundaries[i]+x_boundaries[i+1])/2, N[i]/1e3,
                f'{N[i]/1e3:.1f} kN', ha='center', va='bottom' if N[i]>0 else 'top',
                fontsize=11, fontweight='bold')
axes[1].axhline(0, color='k', linewidth=0.5)
axes[1].set_ylabel('Axial Force [kN]')
axes[1].set_title('(b) Axial Force Distribution')
axes[1].grid(True, alpha=0.3)

# (c) 変位分布
x_disp = [0]
u_disp = [0]
for i in range(3):
    x_end = x_boundaries[i+1]
    if i < 2:
        u_end = u[i]
    else:
        u_end = 0
    x_disp.append(x_end)
    u_disp.append(u_end)

axes[2].plot(x_disp, np.array(u_disp)*1e6, 'b-o', linewidth=2, markersize=8)
axes[2].axhline(0, color='k', linewidth=0.5)
axes[2].set_xlabel('Position x [m]')
axes[2].set_ylabel('Displacement [μm]')
axes[2].set_title('(c) Displacement Distribution')
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('stepped_bar_indeterminate.png', dpi=150, bbox_inches='tight')
plt.show()

このコードの実行結果から、いくつかの重要な特徴が読み取れます。

  1. 断面積が小さい区間ほど応力が大きい — 同じ軸力が小さな断面を通過するため、$\sigma = N/A$ の関係から応力が増大します。区間3($A = 200 \, \text{mm}^2$)の応力が最も大きくなるはずです。
  2. 変位分布は区間ごとの線形関数を接続した折れ線 — 各区間内で軸力が一定のため、変位は位置に対して線形に変化します。両端の変位はゼロ(固定端条件)です。
  3. 荷重は剛性に比例して分配される — 区間の軸剛性 $k = AE/L$ が大きいほど、その区間はより多くの荷重を負担します。

以上の結果は力の方法による手計算と完全に一致しており、変位の方法(剛性法)による数値解法の有効性が確認できます。

次に、温度変化を含む不静定問題をPythonで解いてみましょう。

Pythonによる温度変化の不静定応力

段付き棒に温度変化が加わる場合の不静定応力を計算し、温度変化に対する応力の変化をパラメトリックに可視化します。

import numpy as np
import matplotlib.pyplot as plt

# --- パラメータ ---
L1, L2 = 0.5, 0.5           # 区間の長さ [m]
A1, A2 = 500e-6, 300e-6     # 断面積 [m^2]
E1, E2 = 200e9, 70e9        # ヤング率 [Pa](鋼 + アルミ)
alpha1, alpha2 = 12e-6, 23e-6  # 線膨張係数 [1/K]

# --- 温度変化の範囲 ---
dT = np.linspace(-100, 200, 300)

# --- 不静定応力の計算 ---
# N = -(alpha1*L1 + alpha2*L2)*dT / (L1/(A1*E1) + L2/(A2*E2))
numerator = -(alpha1 * L1 + alpha2 * L2) * dT
denominator = L1 / (A1 * E1) + L2 / (A2 * E2)
N = numerator / denominator

sigma1 = N / A1  # 区間1(鋼)の応力
sigma2 = N / A2  # 区間2(アルミ)の応力

# --- 変位(境界点の変位)---
u_boundary = alpha1 * dT * L1 + N * L1 / (A1 * E1)

# --- 可視化 ---
fig, axes = plt.subplots(1, 3, figsize=(16, 5))

# (1) 応力 vs 温度変化
axes[0].plot(dT, sigma1/1e6, 'b-', linewidth=2, label='Steel ($\\sigma_1$)')
axes[0].plot(dT, sigma2/1e6, 'r-', linewidth=2, label='Aluminum ($\\sigma_2$)')
axes[0].axhline(0, color='k', linewidth=0.5)
axes[0].axvline(0, color='k', linewidth=0.5)
axes[0].fill_between(dT, 0, sigma1/1e6, where=(sigma1<0), alpha=0.1, color='blue')
axes[0].fill_between(dT, 0, sigma1/1e6, where=(sigma1>0), alpha=0.1, color='blue')
axes[0].set_xlabel('Temperature Change $\\Delta T$ [K]')
axes[0].set_ylabel('Stress [MPa]')
axes[0].set_title('Thermal Stress in Stepped Bar')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# (2) 軸力 vs 温度変化
axes[1].plot(dT, N/1e3, 'k-', linewidth=2.5)
axes[1].axhline(0, color='k', linewidth=0.5)
axes[1].axvline(0, color='k', linewidth=0.5)
axes[1].fill_between(dT, 0, N/1e3, where=(N<0), alpha=0.2, color='red', label='Compression')
axes[1].fill_between(dT, 0, N/1e3, where=(N>0), alpha=0.2, color='blue', label='Tension')
axes[1].set_xlabel('Temperature Change $\\Delta T$ [K]')
axes[1].set_ylabel('Axial Force [kN]')
axes[1].set_title('Axial Force vs Temperature Change')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

# (3) 境界点変位 vs 温度変化
axes[2].plot(dT, u_boundary*1e6, 'g-', linewidth=2.5)
axes[2].axhline(0, color='k', linewidth=0.5)
axes[2].axvline(0, color='k', linewidth=0.5)
axes[2].set_xlabel('Temperature Change $\\Delta T$ [K]')
axes[2].set_ylabel('Boundary Displacement [μm]')
axes[2].set_title('Displacement at Section Boundary')
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('thermal_indeterminate.png', dpi=150, bbox_inches='tight')
plt.show()

# --- 特定温度での数値確認 ---
dT_check = 100  # K
N_check = -(alpha1*L1 + alpha2*L2)*dT_check / denominator
print(f"ΔT = {dT_check} K のとき:")
print(f"  軸力 N = {N_check/1e3:.2f} kN")
print(f"  鋼の応力 σ1 = {N_check/A1/1e6:.2f} MPa")
print(f"  アルミの応力 σ2 = {N_check/A2/1e6:.2f} MPa")

グラフから以下の特徴が読み取れます。

  1. 応力は温度変化に対して線形に変化する — これはフックの法則と熱膨張が線形な関係であることから予想通りです。$\Delta T > 0$(温度上昇)で圧縮応力、$\Delta T < 0$(温度低下)で引張応力が生じます。
  2. 同じ軸力でも断面積が小さいアルミ区間の方が応力が大きい — $\sigma = N/A$ の関係から、断面積の比 $A_1/A_2 = 500/300 \approx 1.67$ 倍だけアルミの応力が大きくなります。異種材料を接合する設計では、弱い方の部材の応力に注意が必要です。
  3. 境界点の変位は温度上昇で正方向(右方向)に動く — 鋼よりもアルミの方が線膨張係数が大きいため、拘束されても全体として温度上昇で右寄りに変形する傾向があります。

ここまでで棒の不静定問題を一通り扱いました。次に、より実用的なはりの不静定問題に進みましょう。はりの場合は曲げ変形を考慮するため、適合条件がたわみや回転角で表されます。

はりの不静定問題

両端固定梁

両端固定梁(fixed-fixed beam) は2次不静定のはりであり、実構造で頻繁に現れます。はりのたわみ境界条件の知識を活用して解きます。

問題設定: 長さ $L$ の両端固定梁の中央($x = L/2$)に集中荷重 $P$ が作用する場合の反力とモーメント反力を求めます。

反力は $R_A, R_B$(鉛直反力)と $M_A, M_B$(モーメント反力)の4つです。つり合い方程式は:

$$ R_A + R_B = P \tag{5} $$

$$ M_A + M_B – R_B L + P \cdot \frac{L}{2} = 0 \tag{6} $$

未知数4個に対して方程式2本なので、2次不静定です。

力の方法による解法:

$M_A$ と $M_B$ を冗長力に選び、両端をピン支持(単純支持梁)にした基本系を考えます。適合条件は「両端での回転角がゼロ」:

$$ \theta_A = 0, \quad \theta_B = 0 \tag{7} $$

単純支持梁の中央に集中荷重 $P$ が作用するときの両端の回転角は、はりのたわみの公式から:

$$ \theta_{A0} = \frac{PL^2}{16EI}, \quad \theta_{B0} = -\frac{PL^2}{16EI} $$

ここで対称性から $|\theta_{A0}| = |\theta_{B0}|$ です。

$M_A$ による回転角(単純支持梁の左端にモーメント $M_A$ を加える):

$$ \theta_{A,M_A} = \frac{M_A L}{3EI}, \quad \theta_{B,M_A} = \frac{M_A L}{6EI} $$

$M_B$ による回転角(右端にモーメント $M_B$ を加える):

$$ \theta_{A,M_B} = \frac{M_B L}{6EI}, \quad \theta_{B,M_B} = \frac{M_B L}{3EI} $$

適合条件 (7) に重ね合わせの原理を適用します。$\theta_A = 0$ の条件:

$$ \frac{PL^2}{16EI} + \frac{M_A L}{3EI} + \frac{M_B L}{6EI} = 0 $$

両辺に $EI/L$ を掛けて整理すると:

$$ \frac{PL}{16} + \frac{M_A}{3} + \frac{M_B}{6} = 0 \tag{8} $$

$\theta_B = 0$ の条件(対称性から $M_A = M_B$ が予想されますが、一般性のために式を立てます):

$$ -\frac{PL^2}{16EI} + \frac{M_A L}{6EI} + \frac{M_B L}{3EI} = 0 $$

$$ -\frac{PL}{16} + \frac{M_A}{6} + \frac{M_B}{3} = 0 \tag{9} $$

式 (8) と (9) の連立方程式を解きます。式 (8) + 式 (9) より:

$$ \frac{M_A + M_B}{2} = 0 \implies M_A = -M_B $$

対称性を考えると、荷重が中央に作用しているので $M_A = M_B$ でもあるはずです。この2条件が同時に成り立つのは $M_A = M_B = 0$ の場合だけ — これは何か間違っていそうです。

実は、モーメントの符号規約を正しく適用する必要があります。中央集中荷重の対称性から $M_A = -M_B$(はり理論の通常の符号規約で、左端は正の固定端モーメント、右端は負)ではなく、固定端モーメントは荷重の作用により左端で 反時計回り、右端で 時計回り に生じます。

対称性から $|M_A| = |M_B|$ として、式 (8) に $M_B = M_A$ を代入します:

$$ \frac{PL}{16} + \frac{M_A}{3} + \frac{M_A}{6} = 0 $$

$$ \frac{PL}{16} + \frac{M_A}{2} = 0 $$

$$ \boxed{M_A = M_B = -\frac{PL}{8}} $$

負号は、固定端モーメントが梁を上に凸に曲げる方向であることを意味します。

つり合い方程式 (5) と対称性から:

$$ \boxed{R_A = R_B = \frac{P}{2}} $$

両端固定梁の最大曲げモーメントは中央で $M_{\text{center}} = R_A \cdot L/2 + M_A = PL/4 – PL/8 = PL/8$ です。単純支持梁の最大曲げモーメント $PL/4$ の半分であり、固定端にすることで最大曲げモーメントを半減できる — これが両端固定梁の実用的な利点です。

連続梁

連続梁(continuous beam) は3つ以上の支持点を持つ梁であり、橋梁や建築物の床梁に広く使われる不静定構造です。

2スパンの連続梁(3支持点)を考えます。左端A(ピン支持)、中間点B(ローラー支持)、右端C(ローラー支持)とし、各スパンに等分布荷重 $w$ が作用するとします。

反力は $R_A, R_B, R_C$ の3つで、つり合い方程式は2本なので1次不静定です。

中間支持点Bの反力 $R_B$ を冗長力に選びます。基本系はAとCで支持された単純支持梁(長さ $L_1 + L_2$)です。

適合条件は「点Bのたわみがゼロ」:

$$ v_B = 0 $$

等分布荷重と冗長力 $R_B$ による点Bのたわみを重ね合わせて求め、この条件から $R_B$ を決定します。

スパンが等しい場合($L_1 = L_2 = L$)、対称性から $R_A = R_C$ です。つり合いと適合条件から:

$$ R_B = \frac{5wL}{4}, \quad R_A = R_C = \frac{3wL}{8} $$

中間支持点Bの反力は単純支持梁2本の反力の和 $wL$ より大きくなります。連続梁では荷重が中間支持点に集中する傾向があり、設計時に注意が必要です。

この計算をPythonで実行し、たわみ曲線と内力図(SFD・BMD)を描いてみましょう。

Pythonによる不静定はりのたわみ曲線

両端固定梁に等分布荷重が作用する場合のたわみ曲線を、力の方法で導かれた解析解と数値解法(直接積分法)の両方で求め、比較します。

import numpy as np
import matplotlib.pyplot as plt

# --- 両端固定梁 + 等分布荷重 ---
L = 4.0    # 梁の長さ [m]
w = 10e3   # 等分布荷重 [N/m]
E = 200e9  # ヤング率 [Pa]
I = 8.33e-6  # 断面二次モーメント [m^4](H鋼200x100相当)
EI = E * I

# --- 解析解(両端固定梁の等分布荷重)---
# 反力: R_A = R_B = wL/2
# 固定端モーメント: M_A = M_B = -wL^2/12
# たわみ: v(x) = (w/(24EI)) * x^2 * (L-x)^2
R_A = w * L / 2
M_A = -w * L**2 / 12

x = np.linspace(0, L, 500)
v_analytical = (w / (24 * EI)) * x**2 * (L - x)**2

# --- 曲げモーメント分布 ---
M_analytical = M_A + R_A * x - w * x**2 / 2

# --- せん断力分布 ---
V_analytical = R_A - w * x

# --- 参考: 単純支持梁のたわみ ---
v_simple = (w / (24 * EI)) * x * (L**3 - 2*L*x**2 + x**3)
M_simple = w * L * x / 2 - w * x**2 / 2

# --- 可視化 ---
fig, axes = plt.subplots(3, 1, figsize=(10, 12))

# (a) たわみ曲線
axes[0].plot(x, v_analytical*1e3, 'b-', linewidth=2.5, label='Fixed-Fixed Beam')
axes[0].plot(x, v_simple*1e3, 'r--', linewidth=2, label='Simply Supported (reference)')
axes[0].set_ylabel('Deflection [mm]')
axes[0].set_title('(a) Deflection Curve')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[0].invert_yaxis()

# 最大たわみの注記
v_max_fixed = w * L**4 / (384 * EI)
v_max_simple = 5 * w * L**4 / (384 * EI)
axes[0].annotate(f'Max: {v_max_fixed*1e3:.3f} mm',
                xy=(L/2, v_max_fixed*1e3), xytext=(L/2+0.5, v_max_fixed*1e3+0.1),
                arrowprops=dict(arrowstyle='->', color='blue'), color='blue', fontsize=10)

# (b) 曲げモーメント図 (BMD)
axes[1].plot(x, M_analytical/1e3, 'b-', linewidth=2.5, label='Fixed-Fixed')
axes[1].plot(x, M_simple/1e3, 'r--', linewidth=2, label='Simply Supported')
axes[1].axhline(0, color='k', linewidth=0.5)
axes[1].fill_between(x, M_analytical/1e3, 0, alpha=0.15, color='blue')
axes[1].set_ylabel('Bending Moment [kN$\\cdot$m]')
axes[1].set_title('(b) Bending Moment Diagram (BMD)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

# (c) せん断力図 (SFD)
axes[2].plot(x, V_analytical/1e3, 'b-', linewidth=2.5, label='Fixed-Fixed')
V_simple = w * L / 2 - w * x
axes[2].plot(x, V_simple/1e3, 'r--', linewidth=2, label='Simply Supported')
axes[2].axhline(0, color='k', linewidth=0.5)
axes[2].fill_between(x, V_analytical/1e3, 0, alpha=0.15, color='blue')
axes[2].set_xlabel('Position x [m]')
axes[2].set_ylabel('Shear Force [kN]')
axes[2].set_title('(c) Shear Force Diagram (SFD)')
axes[2].legend()
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('fixed_beam_deflection.png', dpi=150, bbox_inches='tight')
plt.show()

print("=== 両端固定梁 vs 単純支持梁の比較 ===")
print(f"最大たわみ(両端固定): {v_max_fixed*1e3:.4f} mm")
print(f"最大たわみ(単純支持): {v_max_simple*1e3:.4f} mm")
print(f"たわみの比: {v_max_fixed/v_max_simple:.3f} (固定/単純 = 1/5)")
print(f"\n固定端モーメント: {M_A/1e3:.2f} kN·m")
print(f"中央モーメント(両端固定): {(M_A + R_A*L/2 - w*(L/2)**2/2)/1e3:.2f} kN·m")
print(f"中央モーメント(単純支持): {w*L**2/8/1e3:.2f} kN·m")

グラフから以下の重要な知見が得られます。

  1. 両端固定梁の最大たわみは単純支持梁の1/5 — $v_{\max}^{\text{fixed}} = wL^4 / (384EI)$ に対して $v_{\max}^{\text{simple}} = 5wL^4 / (384EI)$ です。固定端にすることで剛性が大幅に向上することがわかります。
  2. 曲げモーメント分布が大きく異なる — 単純支持梁では中央に最大正モーメントが生じますが、両端固定梁では固定端に負モーメント($-wL^2/12$)が生じ、中央の正モーメント($wL^2/24$)が減少します。固定端の負モーメントは単純支持梁の中央モーメント $wL^2/8$ の2/3の大きさです。
  3. BMDの形状に注目すると、変曲点(モーメントの符号が変わる点)が2箇所存在 します。これは固定端のモーメントと外力のモーメントが打ち消し合う位置であり、$x = 0.2113L$ と $x = 0.7887L$ の近傍にあります。

次に、連続梁の反力計算を行い、連続梁ならではの特徴を確認しましょう。

Pythonによる連続梁の反力計算

2スパンおよび3スパンの連続梁について、連立方程式の解法を用いて反力を計算し、たわみ曲線と曲げモーメント図を描きます。ここでは変位の方法(剛性法)を使った汎用的な数値解法を実装します。

import numpy as np
import matplotlib.pyplot as plt

def solve_continuous_beam(spans, w_list, EI, n_elements_per_span=50):
    """
    連続梁の不静定解析(剛性法)

    Parameters
    ----------
    spans : list of float
        各スパンの長さ [m]
    w_list : list of float
        各スパンの等分布荷重 [N/m]
    EI : float
        曲げ剛性 [N·m^2]
    n_elements_per_span : int
        スパンあたりの要素数

    Returns
    -------
    x_global : array
        全体座標
    v_global : array
        たわみ分布
    M_global : array
        曲げモーメント分布
    reactions : array
        支持点反力
    """
    n_spans = len(spans)
    n_supports = n_spans + 1
    n_elem_total = n_elements_per_span * n_spans
    n_nodes = n_elem_total + 1

    # 全体長
    L_total = sum(spans)
    dx = L_total / n_elem_total

    # 支持点のノードインデックス
    support_nodes = [0]
    cumulative = 0
    for s in spans:
        cumulative += s
        node_idx = int(round(cumulative / dx))
        support_nodes.append(node_idx)

    # 全体剛性行列と荷重ベクトル(はり要素: Euler-Bernoulli)
    n_dof = 2 * n_nodes  # 各ノード: [v, θ]
    K_global = np.zeros((n_dof, n_dof))
    F_global = np.zeros(n_dof)

    for e in range(n_elem_total):
        Le = dx
        # 要素剛性行列
        ke = EI / Le**3 * np.array([
            [12,    6*Le,   -12,    6*Le],
            [6*Le,  4*Le**2, -6*Le, 2*Le**2],
            [-12,  -6*Le,    12,   -6*Le],
            [6*Le,  2*Le**2, -6*Le, 4*Le**2]
        ])

        # 等分布荷重の等価節点力
        x_mid = (e + 0.5) * dx
        # どのスパンに属するか
        cum = 0
        w_e = 0
        for s_idx, s_len in enumerate(spans):
            if x_mid < cum + s_len + 1e-10:
                w_e = w_list[s_idx]
                break
            cum += s_len

        fe = w_e * Le / 2 * np.array([1, Le/6, 1, -Le/6])

        # 自由度マッピング
        dofs = [2*e, 2*e+1, 2*(e+1), 2*(e+1)+1]
        for i in range(4):
            F_global[dofs[i]] += fe[i]
            for j in range(4):
                K_global[dofs[i], dofs[j]] += ke[i, j]

    # 境界条件: 支持点でv=0(ピン支持 / ローラー支持)
    # 拘束自由度: 各支持点のv(鉛直変位)
    constrained_dofs = [2 * node for node in support_nodes]

    free_dofs = [i for i in range(n_dof) if i not in constrained_dofs]

    K_ff = K_global[np.ix_(free_dofs, free_dofs)]
    F_f = F_global[free_dofs]

    u_f = np.linalg.solve(K_ff, F_f)

    # 全変位ベクトルの組み立て
    u_global = np.zeros(n_dof)
    for i, dof in enumerate(free_dofs):
        u_global[dof] = u_f[i]

    # たわみの抽出
    x_global = np.linspace(0, L_total, n_nodes)
    v_global = u_global[0::2]  # 偶数インデックス = たわみ

    # 反力の計算
    F_reaction = K_global @ u_global - F_global
    reactions = np.array([F_reaction[2*node] for node in support_nodes])

    # 曲げモーメントの計算(差分近似)
    theta = u_global[1::2]  # 奇数インデックス = 回転角
    M_global = np.zeros(n_nodes)
    for e in range(n_elem_total):
        Le = dx
        # 要素端モーメント
        u_e = np.array([v_global[e], theta[e], v_global[e+1], theta[e+1]])
        ke = EI / Le**3 * np.array([
            [12,    6*Le,   -12,    6*Le],
            [6*Le,  4*Le**2, -6*Le, 2*Le**2],
            [-12,  -6*Le,    12,   -6*Le],
            [6*Le,  2*Le**2, -6*Le, 4*Le**2]
        ])
        f_e = ke @ u_e
        # 要素左端と右端のモーメント
        M_global[e] += f_e[1] / 2 if e > 0 else f_e[1]
        M_global[e+1] += f_e[3] / 2 if e < n_elem_total - 1 else f_e[3]

    return x_global, v_global, M_global, reactions, support_nodes

# --- 2スパン連続梁 ---
EI = 200e9 * 8.33e-6  # [N·m^2]
spans_2 = [5.0, 5.0]  # 等スパン
w_2 = [10e3, 10e3]     # 等分布荷重 [N/m]

x2, v2, M2, R2, sn2 = solve_continuous_beam(spans_2, w_2, EI)

# --- 3スパン連続梁 ---
spans_3 = [4.0, 5.0, 4.0]
w_3 = [10e3, 10e3, 10e3]

x3, v3, M3, R3, sn3 = solve_continuous_beam(spans_3, w_3, EI)

# --- 可視化 ---
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 2スパン: たわみ
axes[0,0].plot(x2, v2*1e3, 'b-', linewidth=2)
for node in sn2:
    axes[0,0].plot(x2[node], 0, 'k^', markersize=12)
axes[0,0].axhline(0, color='k', linewidth=0.5)
axes[0,0].set_title('2-Span Continuous Beam: Deflection')
axes[0,0].set_ylabel('Deflection [mm]')
axes[0,0].invert_yaxis()
axes[0,0].grid(True, alpha=0.3)

# 2スパン: BMD
axes[0,1].plot(x2, M2/1e3, 'b-', linewidth=2)
axes[0,1].fill_between(x2, M2/1e3, 0, alpha=0.15, color='blue')
axes[0,1].axhline(0, color='k', linewidth=0.5)
axes[0,1].set_title('2-Span Continuous Beam: BMD')
axes[0,1].set_ylabel('Bending Moment [kN$\\cdot$m]')
axes[0,1].grid(True, alpha=0.3)

# 3スパン: たわみ
axes[1,0].plot(x3, v3*1e3, 'r-', linewidth=2)
for node in sn3:
    axes[1,0].plot(x3[node], 0, 'k^', markersize=12)
axes[1,0].axhline(0, color='k', linewidth=0.5)
axes[1,0].set_title('3-Span Continuous Beam: Deflection')
axes[1,0].set_xlabel('Position x [m]')
axes[1,0].set_ylabel('Deflection [mm]')
axes[1,0].invert_yaxis()
axes[1,0].grid(True, alpha=0.3)

# 3スパン: BMD
axes[1,1].plot(x3, M3/1e3, 'r-', linewidth=2)
axes[1,1].fill_between(x3, M3/1e3, 0, alpha=0.15, color='red')
axes[1,1].axhline(0, color='k', linewidth=0.5)
axes[1,1].set_title('3-Span Continuous Beam: BMD')
axes[1,1].set_xlabel('Position x [m]')
axes[1,1].set_ylabel('Bending Moment [kN$\\cdot$m]')
axes[1,1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('continuous_beam.png', dpi=150, bbox_inches='tight')
plt.show()

# --- 反力の表示 ---
print("=== 2スパン連続梁の反力 ===")
support_labels = ['A', 'B', 'C']
for i, r in enumerate(R2):
    print(f"  R_{support_labels[i]} = {r/1e3:.2f} kN")
print(f"  合計: {sum(R2)/1e3:.2f} kN (外力合計: {sum(w_2[j]*spans_2[j] for j in range(len(spans_2)))/1e3:.2f} kN)")

print("\n=== 3スパン連続梁の反力 ===")
support_labels_3 = ['A', 'B', 'C', 'D']
for i, r in enumerate(R3):
    print(f"  R_{support_labels_3[i]} = {r/1e3:.2f} kN")
print(f"  合計: {sum(R3)/1e3:.2f} kN (外力合計: {sum(w_3[j]*spans_3[j] for j in range(len(spans_3)))/1e3:.2f} kN)")

この連続梁の解析結果から、以下の特徴が読み取れます。

  1. 中間支持点の反力が端部よりも大きい — 2スパン連続梁の理論解では $R_B = 5wL/4 = 62.5$ kN に対し、端部反力は $R_A = R_C = 3wL/8 = 18.75$ kN です。中間支持点には外力合計の62.5%が集中します。これは連続梁特有の荷重再分配の結果です。
  2. 中間支持点上で負の曲げモーメント(上面引張)が生じる — BMDを見ると、各スパン内ではモーメントが正ですが、中間支持点上では負のモーメントが生じています。この負モーメントの存在が、各スパン内の正モーメントを減少させる効果を持ちます。
  3. 3スパン連続梁では端スパンと中間スパンで挙動が異なる — 端スパンのたわみは中間スパンより大きくなる傾向があります。これは端部支持条件(ピン支持)の拘束が弱いためです。

連続梁の解析は、カスティリアーノの定理を使っても行えますが、スパン数が増えると剛性法による数値解法が圧倒的に効率的です。

不静定問題の物理的意味

ここまで解法に焦点を当ててきましたが、不静定問題の物理的な意味についても整理しておきましょう。

荷重の再分配

不静定構造の最も重要な特徴は荷重の再分配(load redistribution) です。静定構造では、部材や支持の剛性に関係なく反力が決まりますが、不静定構造では剛性の高い部材がより多くの荷重を負担します。

段付き棒の例を振り返ると、反力は $R_A / R_B = k_1 / k_2$ の比で分配されました。これは「硬い部材が荷重を引き受ける」という直感的な原理を数式で表現したものです。

冗長性と安全性

不静定構造が実際の設計で好まれる理由の一つは冗長性(redundancy) です。1つの支持点が機能しなくなっても、残りの支持点で荷重を支え続けることができます。静定構造では1つの支持を失うと即座に機構(不安定構造)になりますが、$n$ 次不静定の構造は $n$ 個の支持を失っても安定を維持できます。

航空機の構造はフェイルセーフ設計として、意図的に不静定次数を高くすることで、部分的な破損が全体の崩壊につながらないようにしています。

温度変化と初期ひずみの影響

静定構造では温度変化や製作誤差による変形は自由に起こり、応力を生じません。しかし、不静定構造ではこれらの変形が拘束されるため、外力がなくても自己応力(self-stress) が生じます。

これは設計上の注意点です。橋梁が温度変化を受ける場合、適切な伸縮継手を設けて不静定次数を下げる(部分的に静定にする)設計も行われます。熱応力は不静定構造では常に考慮すべき問題です。

変形適合の物理的意味

適合条件は「構造物が変形した後もつながったままである」ことを数学的に表現したものです。棒の両端固定問題では「全体の伸びがゼロ」、連続梁では「支持点のたわみがゼロ」が適合条件でした。

これらは当たり前のことに見えますが、力の方法では冗長力を取り除いた基本系を考えるため、一度構造を「切り離し」ています。適合条件は、切り離した構造を元に戻すための条件なのです。

次に、不静定問題の解法をさらに一般化した剛性法の実装を見てみましょう。

一般的な不静定はりソルバー

前節の連続梁ソルバーをより一般化し、集中荷重と等分布荷重の両方を扱えるはりの不静定解析ソルバーを実装します。固定端(回転拘束)も扱えるようにします。

import numpy as np
import matplotlib.pyplot as plt

class BeamSolver:
    """Euler-Bernoulliはりの不静定解析ソルバー(剛性法)"""

    def __init__(self, L, EI, n_elem=100):
        """
        Parameters
        ----------
        L : float  梁の全長 [m]
        EI : float 曲げ剛性 [N·m^2]
        n_elem : int 要素数
        """
        self.L = L
        self.EI = EI
        self.n_elem = n_elem
        self.n_nodes = n_elem + 1
        self.dx = L / n_elem
        self.n_dof = 2 * self.n_nodes
        self.supports = {}      # {node: 'pin', 'roller', 'fixed'}
        self.point_loads = []   # [(node, force)]
        self.dist_loads = []    # [(x_start, x_end, w)]

    def add_support(self, x, stype='pin'):
        """支持条件の追加"""
        node = int(round(x / self.dx))
        self.supports[node] = stype

    def add_point_load(self, x, P):
        """集中荷重の追加"""
        node = int(round(x / self.dx))
        self.point_loads.append((node, P))

    def add_distributed_load(self, x_start, x_end, w):
        """等分布荷重の追加"""
        self.dist_loads.append((x_start, x_end, w))

    def solve(self):
        """剛性法による求解"""
        dx = self.dx
        K = np.zeros((self.n_dof, self.n_dof))
        F = np.zeros(self.n_dof)

        # 全体剛性行列の組み立て
        for e in range(self.n_elem):
            ke = self.EI / dx**3 * np.array([
                [12,    6*dx,   -12,    6*dx],
                [6*dx,  4*dx**2, -6*dx, 2*dx**2],
                [-12,  -6*dx,    12,   -6*dx],
                [6*dx,  2*dx**2, -6*dx, 4*dx**2]
            ])
            dofs = [2*e, 2*e+1, 2*(e+1), 2*(e+1)+1]
            for i in range(4):
                for j in range(4):
                    K[dofs[i], dofs[j]] += ke[i, j]

        # 集中荷重
        for node, P in self.point_loads:
            F[2*node] += P

        # 等分布荷重
        for x_s, x_e, w in self.dist_loads:
            for e in range(self.n_elem):
                x_mid = (e + 0.5) * dx
                if x_s <= x_mid <= x_e:
                    fe = w * dx / 2 * np.array([1, dx/6, 1, -dx/6])
                    dofs = [2*e, 2*e+1, 2*(e+1), 2*(e+1)+1]
                    for i in range(4):
                        F[dofs[i]] += fe[i]

        # 境界条件の適用
        constrained = []
        for node, stype in self.supports.items():
            constrained.append(2*node)  # たわみ = 0
            if stype == 'fixed':
                constrained.append(2*node + 1)  # 回転角 = 0

        free = [i for i in range(self.n_dof) if i not in constrained]
        u = np.zeros(self.n_dof)
        u[free] = np.linalg.solve(K[np.ix_(free, free)], F[free])

        # 反力
        reactions = K @ u - F

        # 結果の格納
        self.x = np.linspace(0, self.L, self.n_nodes)
        self.v = u[0::2]
        self.theta = u[1::2]
        self.reactions = {node: reactions[2*node] for node in self.supports}
        self.moment_reactions = {node: reactions[2*node+1]
                                 for node, s in self.supports.items() if s == 'fixed'}

        # 曲げモーメントの計算
        self.M = np.zeros(self.n_nodes)
        for e in range(self.n_elem):
            ue = np.array([self.v[e], self.theta[e], self.v[e+1], self.theta[e+1]])
            ke = self.EI / dx**3 * np.array([
                [12,    6*dx,   -12,    6*dx],
                [6*dx,  4*dx**2, -6*dx, 2*dx**2],
                [-12,  -6*dx,    12,   -6*dx],
                [6*dx,  2*dx**2, -6*dx, 4*dx**2]
            ])
            fe = ke @ ue
            if e == 0:
                self.M[e] = fe[1]
            else:
                self.M[e] = (self.M[e] + fe[1]) / 2 if self.M[e] != 0 else fe[1]
            self.M[e+1] = fe[3]

        return self

# --- 応用例: プロッツ梁(一端固定・他端ピン + 集中荷重)---
L = 6.0
EI = 200e9 * 8.33e-6

beam = BeamSolver(L, EI, n_elem=200)
beam.add_support(0, 'fixed')
beam.add_support(L, 'pin')
beam.add_point_load(L/3, -50e3)   # 下向き50kN

result = beam.solve()

fig, axes = plt.subplots(2, 1, figsize=(10, 8))

axes[0].plot(result.x, result.v*1e3, 'b-', linewidth=2.5)
axes[0].axhline(0, color='k', linewidth=0.5)
axes[0].set_ylabel('Deflection [mm]')
axes[0].set_title('Propped Cantilever: Deflection')
axes[0].invert_yaxis()
axes[0].grid(True, alpha=0.3)

axes[1].plot(result.x, result.M/1e3, 'b-', linewidth=2.5)
axes[1].fill_between(result.x, result.M/1e3, 0, alpha=0.15, color='blue')
axes[1].axhline(0, color='k', linewidth=0.5)
axes[1].set_xlabel('Position x [m]')
axes[1].set_ylabel('Bending Moment [kN$\\cdot$m]')
axes[1].set_title('Propped Cantilever: BMD')
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('propped_cantilever.png', dpi=150, bbox_inches='tight')
plt.show()

print("=== プロッツ梁の反力 ===")
for node, r in result.reactions.items():
    x_pos = node * result.dx
    print(f"  x = {x_pos:.1f} m: R = {r/1e3:.2f} kN")
for node, m in result.moment_reactions.items():
    x_pos = node * result.dx
    print(f"  x = {x_pos:.1f} m: M = {m/1e3:.2f} kN·m")

このソルバーの実行結果を見ると、プロッツ梁(propped cantilever, 一端固定・他端ピン支持)は1次不静定であり、集中荷重による曲げモーメント分布に固定端モーメントの効果が加わっています。

  1. たわみ曲線が非対称 — 固定端($x = 0$)では回転角がゼロですが、ピン支持端($x = L$)では回転角が自由です。そのため最大たわみの位置は梁の中央ではなく、ピン支持寄りにずれます。
  2. 固定端にモーメント反力が生じる — これが不静定の本質であり、この追加の拘束によって梁全体のたわみが抑制されています。固定端モーメントの大きさは、力の方法で適合条件(ピン支持端のたわみ = 0)から計算できます。
  3. このソルバーは任意の支持条件と荷重に対応add_supportadd_point_loadadd_distributed_load の組み合わせで、さまざまな不静定はりを解くことができます。

不静定問題を解く際の注意点

符号規約の統一

不静定問題では複数の方程式を連立するため、符号規約の不統一が致命的な誤りを招きます。最初に以下を明確に定めましょう。

  • 力の正方向(右向き正 / 上向き正)
  • モーメントの正方向(反時計回り正)
  • 変位の正方向(力の正方向と一致させる)
  • 伸びを正、縮みを負

冗長力の選び方

力の方法では冗長力の選び方に自由度がありますが、以下の点に注意します。

  • 基本系が不安定構造にならないこと
  • 対称性を利用できる冗長力を選ぶと計算が簡単になる
  • 柔軟性係数の計算が容易な冗長力を選ぶ

検算の方法

不静定問題の解が正しいかを確認する方法:

  1. つり合いの検算 — 求めた反力が力のつり合いとモーメントのつり合いを満たすか確認します。
  2. 適合条件の検算 — 求めた変形が境界条件を満たすか確認します。
  3. 特殊ケースの確認 — 対称荷重、一様断面など、答えが既知の特殊ケースに帰着するか確認します。
  4. 次元の確認 — 全ての式で次元が一致しているか確認します。

重ね合わせの原理の前提

力の方法は重ね合わせの原理に基づいているため、以下の条件が必要です:

  • 線形弾性フックの法則が成り立つ範囲内であること
  • 微小変形 — 変形による幾何学的変化が無視できること
  • 定常(静的)問題 — 動的効果を含まないこと

降伏や座屈などの非線形現象が関わる場合は、増分法や反復法が必要になります。

発展的な話題

多次不静定問題

本記事では主に1次や2次の不静定問題を扱いましたが、実際の構造物はもっと高次の不静定問題になります。例えば、複数階建ての建物のラーメン構造は数十次から数百次の不静定です。

力の方法では不静定次数の数だけ連立方程式を解く必要があるため、高次になると手計算は非現実的です。そこで変位の方法が威力を発揮します。有限要素法は変位の方法を一般化したものであり、数千自由度の問題も効率的に解けます。

マトリクス構造解析

本記事のPythonコードで実装した剛性法は、マトリクス構造解析の基本形です。実用的な構造解析ソフトウェア(NASTRAN、ABAQUS、Ansysなど)はこの考え方を精緻化・大規模化したものです。

マトリクス構造解析では:

  1. 各要素の剛性行列を計算
  2. 全体座標系に変換して重ね合わせ
  3. 境界条件を適用
  4. 連立方程式を解く
  5. 後処理で応力や内力を計算

という手順を体系的に実行します。この枠組みは、棒・はり・平面応力・平面ひずみ・板・殻など、あらゆる構造要素に適用できます。

エネルギー法との関係

力の方法はカスティリアーノの定理と密接に関連しています。カスティリアーノの第2定理を使うと、柔軟性係数 $f$ をひずみエネルギーの微分として体系的に計算できます:

$$ f_{ij} = \frac{\partial^2 U}{\partial X_i \partial X_j} $$

ここで $U$ は構造全体のひずみエネルギー、$X_i$ は冗長力です。この定式化は、複雑な構造の柔軟性係数を統一的に求める手法として有用です。

まとめ

本記事では、不静定問題の基礎から解法、Pythonによる数値解析まで体系的に解説しました。

  • 静定と不静定の判別: つり合い方程式の数と未知反力の数を比較し、不静定次数を求める
  • 力の方法: 冗長力を選び、適合条件(変形の条件)から冗長力を決定する古典的手法
  • 変位の方法: 節点変位を未知数として剛性方程式を解く、コンピュータ解析の基盤となる手法
  • 棒の不静定問題: 段付き棒では $R = Pk/(k_1 + k_2)$ の形で荷重が剛性比に応じて分配される
  • 温度変化の不静定: 拘束がある場合、熱膨張が自己応力を生じる
  • はりの不静定問題: 両端固定梁は最大たわみが単純支持梁の1/5、連続梁は中間支持点に荷重が集中する
  • 物理的意味: 不静定構造は剛性に応じた荷重再分配と冗長性による安全性を持つ

不静定問題の解法は「力のつり合い + 変形の適合条件 + 材料の構成則」という3本柱に立脚しています。この考え方を理解すれば、棒・はり・トラスに限らず、あらゆる弾性構造の解析に応用できます。

次のステップとして、以下の記事も参考にしてください。