通過点指定の懸垂線

 懸垂線の両端の位置だけでなく、通過点を指定した場合の懸垂線の長さ、カテナリー数を計算します。

 左図X2,Y2が通過を指定した位置です。
最初に、三点を結ぶ放物線距離をけいさんします。
これを、最初の懸垂線の長さとします。
三点を通る懸垂線は、必ず放物線で結んだ長さより長くなるので、少しずつ長くして、指定点を通るようにします。
円であれば、連立方程式を解けば良いのですが、能力不足で、解けそうにないので、PCの計算速度に頼ることにしました。



 左図がプログラムの実行画面です。
通常の懸垂線プログラムに、途中の通過点を指定する入力項目を追加してあります。
まず、最初に直線で結んだ距離を計算し、両端の直線距離と等しくないか確認します。
長ければ、最初に、近似値として放物線で結んだ長さを計算し、仮の長さとして、カテナリー数を計算します。
次に、カテナリー数を微調整して、懸垂線として、指定点を通るカテナリー数を求めます。


プログラム

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  VclTee.TeeGDIPlus, VCLTee.TeEngine, VCLTee.Series, VCLTee.TeeProcs,
  VCLTee.Chart;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Panel1: TPanel;
    W_Edit: TLabeledEdit;
    h_Edit: TLabeledEdit;
    S_Edit: TLabeledEdit;
    Chart1: TChart;
    Series1: TLineSeries;
    Series2: TPointSeries;
    Button1: TButton;
    CheckBox1: TCheckBox;
    Series3: TLineSeries;
    Series4: TPointSeries;
    R_Edit: TLabeledEdit;
    J_Edit: TLabeledEdit;
    Series5: TPointSeries;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    function datainput: boolean;
    procedure Drawing;
    function f(Ct, S, sL2mh2: double): double;
    function dfdc(Ct, S: double): double;
    function inversed(S, sL2mh2: double): double;
    function inverse_newton(S, sL2mh2: double): double;
    function solve_a(S, h, L: double): double;
    function calc_C: double;
    function calc_C_Def(Cs: double): double;
    function cubic_equation_length: double;
    function calc_arc(a1, b1, c1: double): double;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
uses
  system.Math;

var
  S : double;     // 支持間 m
  R : double;     // 中間点
  h : double;     // 高低差 m
  j : double;     // 中間点高さ
  W : double;     // 線自重 kgw/m
  T : double;     // 線張力 kgf
  C : double;     // カテナリー数
  d : double;     // 弛度   m
  m : double;     // 最大たるみ位置 m
  dm :double;     // 最大たるみ位置 弛度  m
  x : double;     // 斜弛度地点までの距離 m
  L : double;     // 線実長

// 入力処理
function TForm1.datainput: boolean;
var
  ch: integer;
begin
  result := false;
  val(R_edit.Text, R, ch);
  if (ch <> 0) or (R <= 0) then begin
    application.MessageBox('中間点に間違いがあります。','注意',0);
    exit;
  end;
  val(j_edit.Text, j, ch);
  if (ch <> 0) then begin
    application.MessageBox('中間点高さに間違いがあります。','注意',0);
    exit;
  end;
  val(S_edit.Text, S, ch);
  if (ch <> 0) or (S <= 0) then begin
    application.MessageBox('支持間に間違いがあります。','注意',0);
    exit;
  end;
  val(h_edit.Text, h, ch);
  if ch <> 0 then begin
    application.MessageBox('支持点高さに間違いがあります。','注意',0);
    exit;
  end;
  if h / S * R <= j then begin
    application.MessageBox('中間点の高さが高すぎます。','注意',0);
    exit;
  end;
  if (R >= S) or (R <= 0) then begin
    application.MessageBox('中間点の位置が支持間の外側です。','注意',0);
    exit;
  end;
  val(W_edit.Text, W, ch);
  if (ch <> 0) or (W <= 0) then begin
    application.MessageBox('線自重に間違いがあります。','注意',0);
    exit;
  end;
  result := true;
end;

// 懸垂線作図
// 低い方の支持位置基準
procedure TForm1.Drawing;
const
  K = 500;
var
  xn  : double;
  x, dx : double;
  y   : double;
  i   : integer;
begin
  dx := S / K;
  // 懸垂線作図
  for i := 0 to K do begin
    xn := i * dx;                     // 計算位置
    x := abs(xn - m);                 // 最大弛み位置からの距離
    y := C * (cosh(x / C) - 1) - dm;  // 高さ 低い方の支持位置を基準として計算
    Series1.AddXY(xn, y);
  end;
  // 斜弛み位置通過接線
  Series3.AddXY(0, 0 - d);            // 斜弛み位置通過傾斜線 低い方
  Series3.AddXY(S, h - d);            // 斜弛み位置通過傾斜線 高い方
  // 支持位置 斜弛度地点 丸表示
  Series4.AddXY(0, 0);                // 低い方の支持位置
  Series4.AddXY(R, j);
  Series4.AddXY(S, h);                // 高い方の支持位置
  x := m + C * arcsinh(h / S);        // 斜弛度地点距離 x
  y := h / S * x - d;                 // 斜弛度y座標
  Series5.AddXY(x, y);                // 斜弛度xy
end;

// --------- カテナリー数計算 ニュートン法 -----------------------

// 誤差計算 2.0 * Ct * sinh(S / (Ct * 2.0)) = sqrt(L^2-h^2)
function TForm1.f(Ct, S, sL2mh2: double): double;
begin
  result := 2.0 * Ct * sinh(S / (2.0 * Ct)) + sL2mh2;
end;

// 微分計算 ∂/∂Ct
function TForm1.dfdc(Ct, S: double): double;
begin
  result := 2.0 * sinh(S / (2.0 * Ct)) - S * cosh(S / (2.0 * Ct)) / Ct;
end;

// カテナりー数近似値計算
function TForm1.inversed(S, sL2mh2: double): double;
var
  a0, b0, c0: double;
begin
  a0 := sL2mh2 + S;
  b0 := power(S, 3) / 24;
  c0 := power(S, 5) / 1920;
  result := sqrt((-b0 - sqrt(b0 * b0 - 4 * a0 * c0)) / 2 / a0);
end;

// カテナりー数計算
function TForm1.inverse_newton(S, sL2mh2: double): double;
const
  Kp  = 1000;
var
  Ct      : double;
  fvalue  : double;
  eps     : double;
  i, n    : integer;
begin
  eps := 1.0E-15 * L;                                     // 判定値
  Ct := inversed(S, sL2mh2);                              // カテナりー数近似値計算
  fvalue := f(Ct, S, sL2mh2);                             // 誤差計算
  n := 0;
  for i := 0 to Kp do begin
    Ct := Ct - fvalue / dfdc(Ct, S);                      // ニュートン法漸化計算
    fvalue := f(Ct, S, sL2mh2);                           // 誤差計算
    inc(n);                                               // カウンターインクリメント
    if abs(fvalue) < eps then break;                      // 収束判定
  end;
  memo1.Lines.Add('カテナリー数初期値Loop数  = ' + intTostr(n));
  memo1.Lines.Add('C 修正値 = ' + floatTostr(abs(fvalue)));
  if n > kp then begin
    memo1.Lines.Add('カテナリー数収束せずループ数超えました。');
    memo1.Lines.Add('線の長さが直線距離に近すぎるか長すぎます。');
    Ct := 0;
  end;
  if Ct < 0 then begin
    memo1.Lines.Add('線の長さが直線距離に近すぎます。');
  end;
  result := Ct;
end;

// ニュートン法計算スタート
function TForm1.solve_a(S, h, L: double): double;
begin
  result := inverse_newton(S, -sqrt(L * L - h * h));
end;

//--------------------------------------------------------------

// 弓形部長さ計算
// 右と左に分けて計算
function TForm1.calc_arc(a1, b1, c1: double): double;
var
  L0, L1, L2 : double;
  s1, s2    : double;
  a3, b3    : double;
  a4, b4    : double;
  xc0, yc0  : double;
begin
  xc0 := -b1 / a1 / 2;                               // 放物線 変曲点X  中心位置
  yc0 := xc0 * xc0 * a1 + xc0 * b1 + c1;             //        変曲点y  中心位置
  b3 := abs(0 - xc0) * 2;
  a3 := abs(0 - yc0);                               // 放物線のx1の高さ
  if a3 <> 0 then begin
    s1 := sqrt(b3 * b3 + 16 * a3 * a3);
    L1 := s1 / 2 + b3 * b3 / 8 / a3 * ln((4 * a3 + s1) / b3);
  end
  else L1 := 0;
  b4 := abs(S - xc0) * 2;
  a4 := abs(h - yc0);                              // 放物線のx2の高さ
  if a4 <> 0 then begin
    s2 := sqrt(b4 * b4 + 16 * a4 * a4);
    L2 := s2 / 2 + b4 * b4 / 8 / a4 * ln((4 * a4 + s2) / b4);
  end
  else L2 := 0;
  if (0 - xc0) * (S - xc0) > 0 then L0 := abs(L2 - L1) / 2
                               else L0 := (L1 + L2) / 2;
  memo1.Lines.Add('線長さ近似値 L’= ' + floatTostr(L0));
  result := L0;
end;

// 三点から二次関数変換と長さ計算
// ax^2 + bx + c = 0
function TForm1.cubic_equation_length: double;
var
  x1, y1  : double;
  x2, y2  : double;
  x3, y3  : double;
  a, b, c : double;
begin
  x1 := 0;
  y1 := 0;
  x2 := R;
  y2 := j;
  x3 := S;
  y3 := h;
  a := ((y1-y2)*(x1-x3)-(y1-y3)*(x1-x2))/((x1-x2)*(x1-x3)*(x2-x3));   // ax^2
  b := (y1-y2)/(x1-x2)-a*(x1+x2);                                     // bx
  c := y1-a*x1*x1-b*x1;                                               // c
  result := calc_arc(a, b, c);      // 三点を結ぶ長さ計算
end;

// カテナりー数計算
function TForm1.calc_C: double;
var
  Lc    : double;
  L0, L1: double;
begin
  L0 := sqrt(R * R + j * j);                          // 中間位置直線距離
  L1 := sqrt((S - R) * (S - R) + (h - j) * (h - j));  // 中間位置と支持位置直線距離
  Lc := sqrt(S * S + h * h);                          // 支持間距離直線距離
  L := L0 + L1;                                       // 直線で結んだ場合の距離
  if (L - Lc) < 1.0E-8 * L then begin
    result := 0;                                      // 距離の差が小さかったら0
    memo1.Lines.Add('線の長さが直線距離に近すぎます。');
  end
  else begin
    L := cubic_equation_length;                       // 二次曲線による近似長さ計算
    result := solve_a(S, h, L);                       // カテナリー数初期値
  end;
end;

// 中間指定位置へのカテナリー数収束計算
function TForm1.calc_C_Def(Cs: double): double;
var
  df, dc: double;
  dh : double;
  Rh : double;
  Rm : double;
  Rx : double;
  Sm, S2: Double;
  CsB   : Double;
  DCF   : boolean;
  n  : integer;
begin
  memo1.Lines.Add('カタナリー数初期値 = ' + floatTostr(Cs));
  application.ProcessMessages;
  df := 1.0E-15 * L;                   // 収束判定値
  S2 := S / 2;
  m := S2 - Cs * arcsinh(h / (2 * Cs * sinh(S2 / Cs)));    // 一番低い位置
  Sm := abs(m);
  Rm := Cs * cosh(Sm / Cs);             // 一番低い位置の高さ計算
  Rx := abs(R - m);                     // 一番低い位置からの距離
  Rh := Cs * cosh(Rx / Cs) - Rm;        // 高さ 低い方の支持位置を基準として計算
  dc := Cs / 125;                       // カテナリー数補正値初期値
  DCF := True;
  if j > Rh then DCF := False;          // 中間点指定高さより低かったらFalse
  n := 0;                               // カウンタークリア
  repeat
    CsB := Cs;                          // カテナリー数バックアップ
    m := S2 - Cs * arcsinh(h / (2 * Cs * sinh(S2 / Cs)));    // 一番低い位置
    Sm := abs(m);
    Rm := Cs * cosh(Sm / Cs);           // 一番低い位置の高さ計算
    Rx := abs(R - m);                   // 一番低い位置からの距離
    Rh := Cs * cosh(Rx / Cs) - Rm;      // 高さ 低い方の支持位置を基準として計算
    dh := Rh - j;                       // 指定位置の高さの差
    if DCF then begin
      if dh > 0 then Cs := Cs - dc;     // 線の位置が高かったらカテナリー数減算
      if dh < 0 then begin              // 線の位置が低くなったら
        Cs := Cs + dc;                  // カテナリー数戻し
        dc := dc * 0.2;                 // 補正値5分の1
        Cs := Cs - dc;                  // カテナリー数新しい補正値で減算
      end;
    end
    else
    begin
      if dh < 0 then Cs := Cs + dc;     // 線の位置が低かったらカテナリー数加算
      if dh > 0 then begin              // 線の位置が高くなったら
        Cs := Cs - dc;                  // カテナリー数戻し
        dc := dc * 0.2;                 // 補正値5分の1
        Cs := Cs + dc;                  // カテナリー数新しい補正値で加算
      end;
    end;
    inc(n);                             // ループカウントインクリメント
    // 収束判定値 ループ回数上限  カテナリー数変化なしで 収束判定
  until (abs(dh) < df) or (n > 1000) or (Csb = Cs);
  memo1.Lines.Add('カタナリー数修正 Loop数  = ' + intTostr(n));
  memo1.Lines.Add('C 修正値 = ' + floatTostr(abs(dc)));
  result := Cs;
end;

// 懸垂線計算の実行
procedure TForm1.Button1Click(Sender: TObject);
var
  s1,Cs  : double;
  lmin, lmax  : double;
  bmin, bmax  : double;
  mg          : double;
begin
  if not datainput then exit;
  button1.Enabled := False;
  application.ProcessMessages;
  memo1.Clear;
  Cs := calc_C;                       // 仮のカテナリー数計算
  if Cs <= 0 then begin
    button1.Enabled := True;
    exit;
  end;
  C := calc_C_Def(Cs);                // 中間指定高さのカテナリー数計算
  memo1.Lines.Add('カテナリー数   C= ' + floatTostr(C));
  T := C * W;
  memo1.Lines.Add('水平張力 Kgf  T= ' + floatTostr(T));
  m := S / 2 - C * arcsinh(h / (2 * C * sinh(S / 2 / C)));
  memo1.Lines.Add('一番低い位置距離 m= ' + floatTostr(m));
  s1 := abs(m * 2);
  dm := C * (cosh(s1 / 2 / C) - 1);
  memo1.Lines.Add('一番低い位置弛度 dm= ' + floatTostr(dm));
  x := m + C * arcsinh(h / S);
  memo1.Lines.Add('斜弛度位置   x=' + floatTostr(x));
  d := x / S * h + C * (cosh(m / C) - cosh(arcsinh(h / S)));
  memo1.Lines.Add('斜弛度    d=' + floatTostr(d));
  L := C * (sinh(m / C) + sinh((S - m) / C));
  memo1.Lines.Add('線実長    L=' + floatTostr(L));
  // グラフスケール計算
  if h < 0 then begin
    lmin := h - d;
    lmax := 0;
  end
  else begin
    lmin := -d;
    lmax := h;
  end;
  bmin := 0;
  bmax := S;
  // グラフ縦横スケール設定
  if bmax > lmax - lmin then begin
    mg := (bmax - (lmax - lmin)) / 2;
    lmax := lmax + mg;
    lmin := lmin - mg;
  end
  else begin
    mg := ((lmax - lmin) - bmax) / 2;
    bmin := bmin - mg;
    bmax := bmax + mg;
  end;
  Series1.Clear;
  Series2.Clear;
  Series3.Clear;
  Series4.Clear;
  Series5.Clear;
  application.ProcessMessages;
  if checkbox1.Checked then begin
    Series2.AddXY(bmin, lmax);
    Series2.AddXY(bmin, lmin);
    Series2.AddXY(bmax, lmin);
  end;
  application.ProcessMessages;
  Drawing;
  button1.Enabled := True;
end;

end.

    download catenary_3point.zip

各種プログラム計算例に戻る

      最初に戻る