Rでムーディー線図

本ポストはこちらの続きです。

Rでコールブルック・ホワイトの式
const typesetMath = (el) => { if (window.MathJax) { // MathJax Typeset window.MathJax.typeset(); } else if (window.katex...

ムーディー線図とは

ムーディー線図(Moody diagram)は、管路の流れについて、管摩擦係数 \(\lambda\) をレイノルズ数 \(Re\) と相対粗度 \(\varepsilon/D_H\) から読み取るための図表です。

使い方は次のとおりです。

  1. 横軸で \(Re\) の位置を決めます。
  2. 右側の縦軸の相対粗度 \(\varepsilon/D_H\) に対応する曲線をたどります。
  3. 曲線上で \(Re\) に対応する点の高さを、左側の縦軸から \(\lambda\) として読みます。

求めた \(\lambda\) は、ダルシー・ワイスバッハの式で管路の損失水頭 \(h_f\) を求める際に利用されます。

\[
\begin{eqnarray}
h_f &=& \lambda\,\frac{L}{D_H}\,\frac{v^2}{2g}
\end{eqnarray}
\tag{1}\]

ここで、各変数の定義は次のとおりです。

  • \(h_f\) : 摩擦による損失水頭 [m]
  • \(L\) : 管の長さ [m]
  • \(v\) : 管内の平均流速 [m/s]
  • \(g\) : 重力加速度 [m/s\(^2\)]

\(Re\) は、流体の動粘度 \(\nu\) [m\(^2\)/s] を使って \(Re = v\,D_H/\nu\) と表されます。

ムーディー線図は、流れの状態によって、次の3つの領域に分けて読むことができます。

  • 左側の層流域
  • 中央の臨界域(灰色の帯)
  • 右側の乱流域

層流

\(Re\) が小さい(\(Re \lesssim 2300\))と、流体は管軸に平行な層になって整然と流れます。

この状態を層流といいます。

円管の層流では、\(\lambda\) が次の式で求められます。

\[
\begin{eqnarray}
\lambda &=& \frac{64}{Re}
\end{eqnarray}
\tag{2}\]

式 2 には管壁の粗さ \(\varepsilon\) が含まれていません。

そのため、層流域では相対粗度によらず、1本の直線になります(両対数のグラフでは、傾き \(-1\) の直線です)。

Figure 1 の赤い実線が該当します。

なお、係数の 64 は円管の場合の値で、断面が円でない管では、形状によって別の値になります。

臨界域

層流から乱流へ移り変わる \(2300 \lesssim Re \lesssim 4000\) の範囲を、臨界域といいます(Figure 1 の灰色の帯)。

この範囲では、流れが層流と乱流の間を不規則に行き来するため、\(\lambda\) は一意に決まりません。

コールブルック・ホワイトの式も、この範囲では適用できません。

  • 図の赤い破線は、層流の直線 \(\lambda = 64/Re\) を、参考として \(Re = 4000\) まで延ばしたものです。この範囲で実際の \(\lambda\) がこの線に乗るわけではありません。
  • 臨界域の境目の値は、正確に決まったものではありません。\(Re = 2300\) は円管で広く使われる目安であり、管の入口の形や外乱の大きさによって、遷移が起こる \(Re\) は変わります。

乱流と滑面管の破線

\(Re \gtrsim 4000\) の乱流域では、相対粗度ごとの曲線が右下がりに描かれ、コールブルック・ホワイトの式で求めた値になります。曲線は、次のように変化します。

  • \(Re\) が小さい側

    • 管壁のすぐ近くには、粘性が支配する薄い層(粘性底層)ができます。粗さがこの層の中に収まっていれば、粗さの影響は表に出ません。曲線は、後述の滑面管の線に沿って右下がりになります。
  • 中間の領域(遷移域)

    • \(Re\) が大きくなると粘性底層が薄くなり、粗さの影響が出てきます。曲線は滑面管の線から離れ、\(\lambda\) は \(Re\) にも \(\varepsilon/D_H\) にも依存します。
  • \(Re\) が大きい側(完全粗面域)

    • 曲線が水平に近づき、\(\lambda\) は \(\varepsilon/D_H\) だけで決まります。粘性の項が無視できるためです。

Figure 1 の黒い破線は滑面管(\(\varepsilon/D_H = 0\)、粗さがない管)の曲線です。コールブルック・ホワイトの式で粗さの項が消えた次の式から求められます。

\[
\begin{eqnarray}
\frac{1}{\sqrt{\lambda}} &=& -2\log_{10}\left(\frac{2.51}{Re\sqrt{\lambda}}\right)
\end{eqnarray}
\tag{3}\]

粗さがある管は、同じ \(Re\) でも滑面管より \(\lambda\) が大きくなります。

そのため、この破線は、すべての曲線の下限になります。

実用的な管でも、\(Re\) が小さい範囲では粗さの影響が小さく、曲線がこの破線にほぼ重なります。

\(4000 < Re < 10^5\) 程度の範囲では、滑面管の \(\lambda\) はブラジウスの式 \[\lambda = 0.3164\,Re^{-1/4} \tag{4}\] で近似できることが知られています。

ムーディー線図の描画

横軸をレイノルズ数 \(Re\)、左側の縦軸を管摩擦係数 \(\lambda\)、右側の縦軸を相対粗度 \(\varepsilon/D_H\) としたムーディー線図を、前回の投稿で定義した colebrook_white() で描きます。

  • 乱流域(\(Re \geq 4000\))の曲線は、相対粗度ごとにコールブルック・ホワイトの式から求めます。
  • 層流の直線 \(\lambda = 64/Re\) は \(Re = 4000\) まで延ばし、\(Re \leq 2300\) を実線、\(2300 \leq Re \leq 4000\) を破線で描きます。
  • 右側の縦軸は、\(Re = 10^8\) における曲線の右端の高さ(\(\lambda\))を、\(\varepsilon/D_H\) に変換して目盛りにしています。そのため、各曲線の右端に、対応する相対粗度の目盛りが並びます。
# コールブルック・ホワイトの式で、4変数のうち未指定の1つを求める関数
# lambda : 管摩擦係数(無次元)
# epsilon: 管の絶対粗度 [m]
# D_H    : 管の水力直径 [m]
# Re     : レイノルズ数(無次元)
colebrook_white <- function(lambda = NULL, epsilon = NULL, D_H = NULL, Re = NULL) {
  # 引数の指定状況を確認する
  args <- list(lambda = lambda, epsilon = epsilon, D_H = D_H, Re = Re)
  unknown <- names(args)[vapply(args, is.null, logical(1))]
  if (length(unknown) != 1L) {
    stop("lambda, epsilon, D_H, Re のうち、ちょうど3つを指定してください。")
  }

  # 指定された値の妥当性を確認する
  given <- args[setdiff(names(args), unknown)]
  if (!all(vapply(given, is.numeric, logical(1)))) {
    stop("すべての引数は数値で指定してください。")
  }
  if (any(unlist(given) < 0, na.rm = TRUE)) {
    stop("負の値は指定できません。")
  }
  if (any(unlist(given[setdiff(names(given), "epsilon")]) == 0, na.rm = TRUE)) {
    stop("lambda, D_H, Re に 0 は指定できません(epsilon のみ 0 を許容します)。")
  }

  # 管摩擦係数を求める(陰関数のため uniroot で数値的に解く)
  # x = 1 / sqrt(lambda) とおき、g(x) = 0 の解を求める
  solve_lambda <- function(epsilon, D_H, Re) {
    a <- epsilon / (3.71 * D_H) # 相対粗度に由来する項
    b <- 2.51 / Re # 粘性に由来する項の係数
    if (a >= 1) {
      warning("epsilon / D_H が大きすぎるため、解を求められません。")
      return(NA_real_)
    }
    g <- function(x) x + 2 * log10(a + b * x)
    x <- uniroot(g, interval = c(1e-6, 1e3), tol = 1e-12)$root
    1 / x^2
  }

  result <- if (identical(unknown, "lambda")) {
    # 複数の値を同時に与えた場合も計算できるよう、mapply で要素ごとに解く
    mapply(solve_lambda, epsilon, D_H, Re)
  } else {
    # 陽に解ける3変数は、共通の項 10^(-1/(2*sqrt(lambda))) を使う
    s <- sqrt(lambda)
    p <- 10^(-1 / (2 * s))
    if (identical(unknown, "Re")) {
      denom <- s * (p - epsilon / (3.71 * D_H))
      invalid <- denom <= 0
      value <- 2.51 / denom
    } else if (identical(unknown, "epsilon")) {
      value <- 3.71 * D_H * (p - 2.51 / (Re * s))
      invalid <- value < 0
    } else {
      # 未指定が D_H の場合
      k <- p - 2.51 / (Re * s)
      invalid <- k <= 0 | epsilon == 0
      value <- epsilon / (3.71 * k)
    }
    if (any(invalid, na.rm = TRUE)) {
      warning(
        "与えた3つの値に対して解が存在しない組み合わせがあるため、NA を返します。",
        "(lambda が滑面管の値より小さい、または epsilon = 0 のときの D_H など)"
      )
      value[invalid] <- NA_real_
    }
    value
  }

  # 乱流域の外では式の適用範囲を外れるため、注意を促す
  re_check <- if (identical(unknown, "Re")) result else Re
  if (any(re_check < 4000, na.rm = TRUE)) {
    warning("Re が 4000 未満の値を含みます。コールブルック・ホワイトの式は乱流域(Re >= 4000 程度)が対象です。")
  }

  result
}
library(ggplot2)

# 描画の条件
re_max <- 1e8 # 右側の縦軸の基準にするレイノルズ数
rr_values <- c(
  1e-5, 2e-5, 5e-5, 1e-4, 2e-4, 2.5e-4, 5e-4, 1e-3, 2e-3, 2.5e-3,
  5e-3, 7.5e-3, 1e-2, 1.5e-2, 2e-2, 3e-2, 4e-2, 5e-2
) # 相対粗度 epsilon / D_H
re_turb <- 10^seq(log10(4000), log10(re_max), length.out = 200) # 乱流域のレイノルズ数
re_lam_solid <- 10^seq(log10(600), log10(2300), length.out = 50) # 層流域(実線)のレイノルズ数
re_lam_dash <- 10^seq(log10(2300), log10(4000), length.out = 50) # 臨界域(破線)のレイノルズ数

# 相対粗度ごとの曲線(D_H = 1 とすれば epsilon が相対粗度になる)
curves <- do.call(rbind, lapply(rr_values, function(rr) {
  data.frame(
    Re = re_turb,
    lambda = colebrook_white(epsilon = rr, D_H = 1, Re = re_turb),
    rr = rr
  )
}))

# 滑面管の曲線(epsilon = 0)と、層流の直線(lambda = 64 / Re)
smooth <- data.frame(Re = re_turb, lambda = colebrook_white(epsilon = 0, D_H = 1, Re = re_turb))
laminar_solid <- data.frame(Re = re_lam_solid, lambda = 64 / re_lam_solid)
laminar_dash <- data.frame(Re = re_lam_dash, lambda = 64 / re_lam_dash)

# 右側の縦軸用の変換: Re = re_max での lambda から epsilon / D_H を求める
lambda_to_rr <- function(lambda) {
  suppressWarnings(colebrook_white(lambda = lambda, D_H = 1, Re = re_max))
}

p <- ggplot() +
  # 臨界域(層流から乱流への遷移域)
  annotate("rect", xmin = 2300, xmax = 4000, ymin = 0.006, ymax = 0.11, fill = "grey80", alpha = 0.5) +
  annotate("text", x = sqrt(2300 * 4000), y = 0.0068, label = "臨界域", size = 3.5) +
  annotate("text", x = sqrt(2300 * 4000), y = 0.065, label = "Re = 2300〜4000", size = 3, angle = 90) +
  # 相対粗度ごとの曲線
  geom_line(data = curves, aes(x = Re, y = lambda, group = rr), colour = "steelblue", linewidth = 0.5) +
  # 滑面管
  geom_line(data = smooth, aes(x = Re, y = lambda), colour = "black", linetype = "dashed", linewidth = 0.6) +
  annotate("text", x = 3e6, y = 0.0072, label = "滑面管", size = 3.5, hjust = 0) +
  # 層流
  geom_line(data = laminar_solid, aes(x = Re, y = lambda), colour = "firebrick", linewidth = 0.8) +
  geom_line(data = laminar_dash, aes(x = Re, y = lambda), colour = "firebrick", linewidth = 0.8, linetype = "dashed") +
  annotate("text", x = 640, y = 0.02, label = "層流\nλ=64/Re", size = 3.2, hjust = 0, colour = "firebrick") +
  scale_x_log10(
    breaks = 10^(3:8), # 目盛りの文字は 10^n の位置だけにする
    minor_breaks = as.vector(outer(1:9, 10^(2:7))), # 1〜9 x 10^n の位置に補助線を引く
    labels = function(x) parse(text = paste0("10^", round(log10(x)))), # 指数表記(10^3, 10^4, ...)
    limits = c(600, re_max),
    expand = c(0, 0)
  ) +
  scale_y_log10(
    breaks = c(0.006, 0.008, 0.01, 0.015, 0.02, 0.03, 0.04, 0.05, 0.06, 0.08, 0.1),
    labels = function(x) format(x, scientific = FALSE, trim = TRUE),
    limits = c(0.006, 0.11),
    oob = scales::oob_keep,
    expand = c(0, 0),
    sec.axis = sec_axis(
      lambda_to_rr,
      name = expression(paste("相対粗度 ", epsilon / D[H])), # 数式表記(添え字の H を下付きにする)
      breaks = rr_values,
      labels = function(x) format(x, scientific = FALSE, trim = TRUE, drop0trailing = TRUE)
    )
  ) +
  labs(
    title = "ムーディー線図(コールブルック・ホワイトの式)",
    x = "レイノルズ数 Re",
    y = "管摩擦係数 λ"
  ) +
  theme_bw() +
  theme(
    panel.grid.minor.x = element_line(linewidth = 0.2, colour = "grey90"),
    panel.grid.minor.y = element_blank(),
    axis.text.y.right = element_text(size = 8)
  )

print(p)
Figure 1

計算した管摩擦係数のプロット

colebrook_white() で求めた \(\lambda\) を、ムーディー線図(p)に重ねて描きます。

水が流れる円管を3つ想定し、流速から \(Re\) を求めたうえで、\(\lambda\) を計算します。

  • 動粘度は、水(約20℃)の値 \(\nu = 1.0\times10^{-6}\) m\(^2\)/s としています。
  • 円管の水力直径 \(D_H\) は、管の内径に等しくなります。

\[
\begin{eqnarray}
Re &=& \frac{v\,D_H}{\nu}
\end{eqnarray}
\]

ここで、\(v\) は管内の平均流速 [m/s]、\(\nu\) は流体の動粘度 [m\(^2\)/s] です。

# 計算する条件(水が流れる円管の例)
cases <- data.frame(
  name = c("A: 鋼管", "B: 塩ビ管", "C: コンクリート管"),
  epsilon = c(0.045e-3, 0.0015e-3, 1e-3), # 絶対粗度 [m]
  D_H = c(0.1, 0.05, 0.5), # 水力直径(満水の円管では内径に等しい) [m]
  v = c(2, 1, 3), # 平均流速 [m/s]
  nu = 1.0e-6 # 水の動粘度 [m^2/s]
)

# レイノルズ数、相対粗度、管摩擦係数を求める
cases$Re <- cases$v * cases$D_H / cases$nu
cases$rr <- cases$epsilon / cases$D_H
cases$lambda <- colebrook_white(epsilon = cases$epsilon, D_H = cases$D_H, Re = cases$Re)
cases$label <- sprintf(
  "%s\nRe = %s\nλ = %.4f",
  cases$name, format(cases$Re, big.mark = ",", scientific = FALSE, trim = TRUE), cases$lambda
)
print(cases[, c("name", "epsilon", "D_H", "v", "Re", "rr", "lambda")])

# ラベルの位置(A, B, C の順)。重ならないように、1つずつ指定する
# A, B は曲線の下側の空いている場所に、各点の縦の補助線に沿わせて置く
cases$label_x <- cases$Re * c(1.1, 0.9, 1.2) # ラベルの横位置(Re の 1.1 倍など)
cases$label_y <- c(0.0145, 0.0165, cases$lambda[3] * 1.25) # ラベルの縦位置
cases$label_hjust <- c(0, 1, 0) # 0: ラベルの左端を合わせる、1: 右端を合わせる

# 各条件の相対粗度に対応する曲線(ムーディー線図の曲線の間を補う)
curves_cases <- do.call(rbind, lapply(seq_len(nrow(cases)), function(i) {
  data.frame(
    Re = re_turb,
    lambda = colebrook_white(epsilon = cases$rr[i], D_H = 1, Re = re_turb),
    name = cases$name[i]
  )
}))

p2 <- p +
  # 各条件の曲線
  geom_line(data = curves_cases, aes(x = Re, y = lambda, group = name), colour = "darkorange", linewidth = 0.5) +
  # 点から左側の縦軸と横軸へ、読み取り用の補助線
  geom_segment(
    data = cases, aes(x = 600, xend = Re, y = lambda, yend = lambda),
    colour = "grey40", linetype = "dotted"
  ) +
  geom_segment(
    data = cases, aes(x = Re, xend = Re, y = 0.006, yend = lambda),
    colour = "grey40", linetype = "dotted"
  ) +
  # 計算した点とラベル
  geom_point(data = cases, aes(x = Re, y = lambda), colour = "red", size = 3) +
  geom_label(
    data = cases, aes(x = label_x, y = label_y, label = label, hjust = label_hjust),
    size = 3, fill = "white"
  )

print(p2)
               name epsilon  D_H v      Re      rr     lambda
1           A: 鋼管 4.5e-05 0.10 2  200000 0.00045 0.01855374
2         B: 塩ビ管 1.5e-06 0.05 1   50000 0.00003 0.02099938
3 C: コンクリート管 1.0e-03 0.50 3 1500000 0.00200 0.02352880
Figure 2

以上です。