jw_cad 外部変形 - (802) rubyで楕円弧の長さ(表示画面のスケール)を計測表示する -

外部変形は データのやり取りをテキストファイルで行うので プログラム言語は 自由に選ぶことができます。図形は機能的かつシンプルなため、数多くのユーザーに受け入れられています。

 

rubyで楕円弧の長さ(表示画面のスケール)を計測表示する

その① 台形則

:rubyで楕円弧の長さ(表示画面のスケール)を計測表示する 台形則
@echo off
ruby -x %0 jwc_temp.txt
goto:eof

REM #jww
REM #1ci 長さを計測する円・円弧を指示してください
REM #e

#!ruby -Ks -an -i.bak
def rad(x) #度をラジアンに変換
  x ? x * PI / 180.0 : 0
end
def apa(o, q) #角度補正
  return (90.0 * (2 * ( (o + 1) / 2).to_i - 1) - q) * (-1) ** (o - 1)
end
def arc_l(r, p1, p2, w) #楕円弧の長さ
  #r, p1, p2, w  半径、始角 ゚、終角 ゚、扁平率
  if w > 1
    w = 1.0 / w
    r /= w
    p1 -= 90
    p2 -= 90
  end
  k = sqrt(1.0 - w * w) #離心率
  ek = e(k) #第二種完全楕円積分
  p1 += 360 while p1 < 0
  p1 -= 360 while p1 >= 360
  p2 += 360 while p2 < 0
  p2 -= 360 while p2 >= 360
  return r * 4.0 * ek if p1 == p2
  n1 = 1 + (p1 / 90.0).to_i
  n1 = 4 if n1 > 4
  lp = ek * ( (n1 + 1) % 2) + e(rad(apa(n1, p1)), k) * (-1.0) ** (n1 - 1)
  n2 = 1 + (p2 / 90.0).to_i
  n2 = 4 if n2 > 4
  n1 += 4 if n1 == n2
  n3 = 5 + 2 * ( (n2 - 1) / 2.0).to_i
  lp += ek * ( (n3 - n1) % 4) + e(rad(apa(n2, p2)), k) * (-1.0) ** n2
  lp += 4.0 * ek if lp < 0.0
  return r * lp
end
def e(p, k = nil) #台形則 : 第二種不完全楕円積分
  def fx(x, k)
    return sqrt(1.0 - (k * sin(x)) ** 2)
  end
  if k == nil #第二種完全楕円積分の補正
    k = p
    p = PI / 2
  end
  n = 10000
  h = p / n
  s1 = s2 = 0.0
  for i in 0..n-1
    s1 += fx(x = h * i, k)
    s2 += fx(x + h, k)
  end
  return h / 2.0 * (s1 + s2)
end
BEGIN { include Math }
case $_
  when /^hs/
    hs = {}
    for i in 0..15
      hs[ sprintf("%x", i) ] = $F[i + 1].to_f
    end
  when /^lg[0-9a-f]/
    plg = $_[2, 1].to_s
    $lg = plg if $lg == nil
  when /^ci/
    p = hs[$lg] / hs[plg]
    x, y, r = $F[1..3].map { |z| z.to_f * p }
    p1, p2, w, d = 0, 0, 1, 0
    if $F.size == 8
      p1, p2, w, d = $F[4..7].map { |z| z.to_f }
    end
    if w == 1
      lr = r * rad(p1 == p2 ? 360 : p2 - p1)
    else
      lr = arc_l(r, p1, p2, w)
    end
    puts "h#円弧長 L = %.3f" % lr
end
__END__

 

その② Simpson則

:rubyで楕円弧の長さ(表示画面のスケール)を計測表示する Simpson
@echo off
ruby -x %0 jwc_temp.txt
goto:eof

REM #jww
REM #1ci 長さを計測する円・円弧を指示してください
REM #e

#!ruby -Ks -an -i.bak
def rad(x) #度をラジアンに変換
  x ? x * PI / 180.0 : 0
end
def apa(o, q) #角度補正
  return (90.0 * (2 * ( (o + 1) / 2).to_i - 1) - q) * (-1) ** (o - 1)
end
def arc_l(r, p1, p2, w) #楕円弧の長さ
  #r, p1, p2, w  半径、始角 ゚、終角 ゚、扁平率
  if w > 1
    w = 1.0 / w
    r /= w
    p1 -= 90
    p2 -= 90
  end
  k = sqrt(1.0 - w * w) #離心率
  ek = e(k) #第二種完全楕円積分
  p1 += 360 while p1 < 0
  p1 -= 360 while p1 >= 360
  p2 += 360 while p2 < 0
  p2 -= 360 while p2 >= 360
  return r * 4.0 * ek if p1 == p2
  n1 = 1 + (p1 / 90.0).to_i
  n1 = 4 if n1 > 4
  lp = ek * ( (n1 + 1) % 2) + e(rad(apa(n1, p1)), k) * (-1.0) ** (n1 - 1)
  n2 = 1 + (p2 / 90.0).to_i
  n2 = 4 if n2 > 4
  n1 += 4 if n1 == n2
  n3 = 5 + 2 * ( (n2 - 1) / 2.0).to_i
  lp += ek * ( (n3 - n1) % 4) + e(rad(apa(n2, p2)), k) * (-1.0) ** n2
  lp += 4.0 * ek if lp < 0.0
  return r * lp
end
def e(p, k = nil) #Simpson : 第二種不完全楕円積分
  def fx(x, k)
    return sqrt(1.0 - (k * sin(x)) ** 2)
  end
  if k == nil #第二種完全楕円積分の補正
    k = p
    p = PI / 2
  end
  n = 1000
  x = h = p / (n * 2.0)
  s1 = 0
  s2 = fx(x, k)
  for i in 1..n-1
    s1 += fx(x += h, k)
    s2 += fx(x += h, k)
  end
  return h / 3.0 * (fx(0, k) + fx(p, k) + 4.0 * s2 + 2.0 * s1)
end
BEGIN { include Math }
case $_
  when /^hs/
    hs = {}
    for i in 0..15
      hs[ sprintf("%x", i) ] = $F[i + 1].to_f
    end
  when /^lg[0-9a-f]/
    plg = $_[2, 1].to_s
    $lg = plg if $lg == nil
  when /^ci/
    p = hs[$lg] / hs[plg]
    x, y, r = $F[1..3].map { |z| z.to_f * p }
    p1, p2, w, d = 0, 0, 1, 0
    if $F.size == 8
      p1, p2, w, d = $F[4..7].map { |z| z.to_f }
    end
    if w == 1
      lr = r * rad(p1 == p2 ? 360 : p2 - p1)
    else
      lr = arc_l(r, p1, p2, w)
    end
    puts "h#円弧長 L = %.3f" % lr
end
__END__

 

その③ Gauss-Legendre

:rubyで楕円弧の長さ(表示画面のスケール)を計測表示する Gauss-Legendre
@echo off
ruby -x %0 jwc_temp.txt
goto:eof

REM #jww
REM #1ci 長さを計測する円・円弧を指示してください
REM #e

#!ruby -Ks -an -i.bak
def rad(x) #度をラジアンに変換
  x ? x * PI / 180.0 : 0
end
def apa(o, q) #角度補正
  return (90.0 * (2 * ( (o + 1) / 2).to_i - 1) - q) * (-1) ** (o - 1)
end
def arc_l(r, p1, p2, w) #楕円弧の長さ
  #r, p1, p2, w  半径、始角 ゚、終角 ゚、扁平率
  if w > 1
    w = 1.0 / w
    r /= w
    p1 -= 90
    p2 -= 90
  end
  k = sqrt(1.0 - w * w) #離心率
  ek = e(k) #第二種完全楕円積分
  p1 += 360 while p1 < 0
  p1 -= 360 while p1 >= 360
  p2 += 360 while p2 < 0
  p2 -= 360 while p2 >= 360
  return r * 4.0 * ek if p1 == p2
  n1 = 1 + (p1 / 90.0).to_i
  n1 = 4 if n1 > 4
  lp = ek * ( (n1 + 1) % 2) + e(rad(apa(n1, p1)), k) * (-1.0) ** (n1 - 1)
  n2 = 1 + (p2 / 90.0).to_i
  n2 = 4 if n2 > 4
  n1 += 4 if n1 == n2
  n3 = 5 + 2 * ( (n2 - 1) / 2.0).to_i
  lp += ek * ( (n3 - n1) % 4) + e(rad(apa(n2, p2)), k) * (-1.0) ** n2
  lp += 4.0 * ek if lp < 0.0
  return r * lp
end
def e(p, k = nil) #Gauss-Legendre : 第二種不完全楕円積分
  def fx(x, k)
    return sqrt(1.0 - (k * sin(x)) ** 2)
  end
  if k == nil #第二種完全楕円積分の補正
    k = p
    p = PI / 2
  end
  l = 0
  if $n > 1
    for i in 1..($n / 2).to_i
      l += $h[i] * fx($e[i] * p, k)
    end
  end
  if ($n % 2) == 1
    m  = ($n / 2).to_i + $n % 2
    l += $h[m] * fx($e[m] * p, k) / 2.0 #奇数個の場合
  end
  return l * p
end
def gear(bpa)
  ww = bpa
  if bpa > 1 then ww = 1.0 / bpa end
  if ww == 0 || ww == nil then ww = 0.1 end
  $n = (12 * (1.5 / ww)).to_i
  $n = 300 if $n > 300
  eh($n)
  return bpa
end
def eh(n) # 積分点デ-タ
  $e =
  $h =

  m  = (n / 2).to_i + n % 2
  e1 = n * (n + 1.0)
  for i in 1..m do #DO 1
    t   = (4.0 * i - 1.0) * PI / (4.0 * n + 2.0)
    x0  = (1.0 - (1.0 - 1.0 / n) / (8.0 * n * n)) * cos(t)
    pkm1= 1.0
    pk  = x0
    for k in 2..n do #DO 3
      t1   = x0 * pk
      pkp1 = t1 - pkm1 - (t1 - pkm1) / k + t1
      pkm1 = pk
      pk   = pkp1
    end #DO 3
    den  = 1.0 - x0 * x0
    d1   = n * (pkm1 - x0 * pk)
    dpn  = d1 / den
    d2pn = (2.0 * x0 * dpn - e1 * pk) / den
    d3pn = (4.0 * x0 * d2pn + (2.0 - e1) * dpn) / den
    d4pn = (6.0 * x0 * d3pn + (6.0 - e1) * d2pn) / den
    u  = pk / dpn
    v  = d2pn / dpn
    h  = -1.0 * u * (1.0 + 0.5 * u * (v + u * (v * v - d3pn / (3.0 * dpn))))
    pp = pk + h * (dpn + 0.5 * h * (d2pn + h / 3.0 * (d3pn + 0.25 * h * d4pn)))
    dp = dpn + h * (d2pn + 0.5 * h * (d3pn + h * d4pn / 3.0))
    h -= pp / dp
    xi = x0 + h
    fx = d1 - h * e1 * (pk + 0.5 * h * (dpn + h / 3.0 * (d2pn + 0.25 * h * (d3pn + 0.2 * h * d4pn))))
    wi = 2.0 * (1.0 - xi * xi) / (fx * fx)
    $e[i] = xi
    $h[i] = wi
  end #DO 1
end
BEGIN { include Math }
case $_
  when /^hs/
    hs = {}
    for i in 0..15
      hs[ sprintf("%x", i) ] = $F[i + 1].to_f
    end
  when /^lg[0-9a-f]/
    plg = $_[2, 1].to_s
    $lg = plg if $lg == nil
  when /^ci/
    p = hs[$lg] / hs[plg]
    x, y, r = $F[1..3].map { |z| z.to_f * p }
    p1, p2, w, d = 0, 0, 1, 0
    if $F.size == 8
      p1, p2, w, d = $F[4..7].map { |z| z.to_f }
    end
    if w == 1
      lr = r * rad(p1 == p2 ? 360 : p2 - p1)
    else
      lr = arc_l(r, p1, p2, gear(w))
    end
    puts "h#円弧長 L = %.3f" % lr
end
__END__

 

 

計算精度は Gauss-Legendre による楕円積分が良好です。楕円積分は数値積分のほか級数解も紹介されていましたがいまはリンクぎれのようです。

rubyに書き直したメモがあるので級数解による不完全楕円積分の例を紹介します。

@echo off
ruby -x %~f0
pause
goto:eof

#!ruby -Ks

=begin

○楕円積分 数値計算ノート(リンクは切れています)
  http://www.ab.cyberhome.ne.jp/~naka-kevin/Elliptic-Integral.html
  より 抜粋

楕円積分 (∫φ_0は0からφまでの定積分を表しています)
第1種楕円積分 E1(φ,k)=∫φ_0 1/√(1-k^2 sin^2φ) dφ=φ+(1/2) k^2 S_2(φ)+(1/2)(3/4) k^4 S_4(φ)+ …
第2種楕円積分 E2(φ,k)=∫φ_0 √(1-k^2 sin^2φ) dφ=φ+(-1/2) k^2 S_2(φ)+(-1/2)(3/4) k^4 S_4(φ)+(-1/2)(3/4)(5/6) k^6 S_6(φ)+ …
(ここで∫φ_0 sin^nφ dφ=S_n(φ)と略記)
S_n(φ)は漸化式 S_n(φ) =( (n-1)S_n-2(φ) -sin^(n-1)φcosφ)/nで計算出来る
S_0(φ) =φ
S_2(φ) =(1/2)S_0(φ)-sinφcosφ/2=φ/2-sinφcosφ/2
S_4(φ) =(3/4)S_2(φ)-sin^3φcosφ/4=(3/4)(φ/2-sinφcosφ/2)-sin^3φcosφ/4=(3/8)φ-(1/8)sinφcosφ(2sin^2φ+3)

○上記 ホームページの解説により
 無限級数解も実用的であることがわかりました。

 処理速度、計算精度ともに ガウスールジャンドルの数値積分 程度 と思われます。
 計算誤差の問題はないようです。倍精度なら倍精度で返せるようです。

=end

include Math

def E1(φ, k=nil) #第1種楕円積分
  if k == nil
    k = φ
    φ = PI / 2
  end
  if k < 0 or 1 < k then return print "h#E1 φ, %s 計算できませんでした\n" % k end
  if k > 1-1e-15 then return log( (1.0 + sin(φ)) / (1.0 - sin(φ))) / 2.0 end
  n, f, a, s_n = 2.0, 0.0, 1.0, φ
  c, s = cos(φ), sin(φ)
  j = 0
  while (a * s_n).abs > 1e-20
    f += a * s_n
    a *= (n - 1) / n * k * k
    s_n = ( (n - 1) * s_n - s ** (n - 1) * c) / n
    n += 2
    j += 1
    if j > 30000 then break end
  end
  return f
end

def E2(φ, k=nil) #第2種楕円積分
  if k == nil
    k = φ
    φ = PI / 2
  end
  if k < 0 or 1 < k then return print "h#E2 φ, %s 計算できませんでした\n" % k end
  if k > 1-1e-15 then return sin(φ) end
  n, e, a, s_n = 2.0, 0.0, 1.0, φ
  c, s = cos(φ), sin(φ)
  j = 0
  while (a * s_n).abs > 1e-20
    e += a * s_n
    a *= (n - 3) / n * k * k
    s_n = ( (n - 1) * s_n - s ** (n - 1) * c) / n
    n += 2
    j += 1
    if j > 30000 then break end
  end
  return e
end

φ= PI/6
k = sqrt(1-0.5**2)
p E1(k)     #第1種完全楕円積分
p E1(φ, k) #第1種不完全楕円積分
p E2(k)     #第2種完全楕円積分
p E2(φ, k) #第2種不完全楕円積分

def arc_l(r,p1,p2,w)
  ek = E2(PI/2,k=sqrt(1-w*w))
  if p1 == p2 then p2 += 360 end
  lp = ( (p2-p1).abs/360.0).to_i*4.0*ek
  p1 += 360 while p1 < 0
  p1 -= 360 while p1 >= 360
  p2 += 360 while p2 < 0
  p2 -= 360 while p2 >= 360
  if p1 == p2; return r*lp; end
  n1  = 1+(p1/90.0).to_i
  if n1 > 4; n1 = 4; end
  lp += ek*( (n1+1.0).remainder(2))+E2(apa(n1,p1)*PI/180,k)*(-1.0)**(n1-1)
  n2  = 1+(p2/90.0).to_i
  if n2 > 4; n2 = 4; end
  if n1 == n2; n1 += 4; end
  n3  = 5+2*( (n2-1.0)/2.0).to_i
  lp += ek*( (n3-n1).remainder(4))+E2(apa(n2,p2)*PI/180,k)*(-1.0)**n2
  if lp < 0.0; lp += 4.0 * ek; end
  return r*lp
end
def apa(o,q)
  return (90.0*(2*( (o+1)/2).to_i-1)-q)*(-1)**(o-1)
end

p arc_l(半径=50, 始角=30, 終角=120, 扁平比=0.5)