縄跳び紐との接線その3

 此処では、通過点を指定した接線の値を求めます。

 通過点を指定した、接線は、左図の様に接線が二本になる場合と、一本になる場合、さらに無い場合があります。
通過点は、X座標と、Y座標の値を指定します。


指定点を通る接線がある場合の条件
 A.指定点のY座標が0より大きい時
   1.指定点のX座標が、縄のX範囲内にある場合は、縄の曲線より上にある事
   2.縄の左端点と、指定点間の傾きが、縄の左端点の傾きより小さい事
   3.縄の右端点と、指定点間の傾きが、縄の右端点の傾きより小さい事
     1,2を満足する場合は左一本  1,3を満足する場合は右一本  1,2,3を満足する場合は、二本
   *傾きの大小の範囲は、回転方法で判断します。
 B.指定点のY座標が0より小さい時   2.縄の左端点と、指定点間の傾きが、縄の左端点の傾きより小さい事
   1.指定点のX座標が、縄のX範囲外にある事
   2.縄の左端点と、指定点間の傾きが、縄の左端点の傾きより小さい事
   3.縄の右端点と、指定点間の傾きが、縄の右端点の傾きより小さい事
     接線の数は一本

接点の計算
 接点の計算で、連立方程式をたてることは可能ですが、楕円積分関数が入る為、解くことが難しいので、前記条件で、紐曲線の端点から、傾きを計算、指定点との傾きが一致する点を検索します。
通過指定点と、縄跳び紐の端点が一致する場合は、単に紐曲線端点の傾斜とします。

 計算の収束計算に DBL_EPSILON を使用しています。
DBL_EPSILONは、Cの場合 1 + xE-y = 1 となる時の寸前値で、規格は 1E-9 以下となっています。
実際の計算では、もっと小さな値で C の場合Doubleで 2.22044604925031E-16 、Delphiには DBL_EPSILON の値は設定されていません。

DBL_EPSILONの設定手順です。
x = 1 / 2から始めて x = x / 2 を繰り返し
1 + x = 1 になってしまう x の値を求め、 1 + x > 1 となる最小値として DBL_EPSILON = x * 2  とします。
この場合乗算が速いからと、0.5の使用は避けるようにします、0.51/2には僅かな誤差が生じます。

各精度での値は
  Double   DBL_EPSILON     = 2.22044604925031E-16
  Extended LDBL_EPSILON  = 1.08420217248550443E-19
    Single    FLT_EPSILON   = 1.1920929E-7

プログラム

//-----------------------------------------------------
//  ヤコビの楕円関数と縄跳びの紐計算
// http://fuchino.ddo.jp/yatsugatake/ellipticx.pdf
// http://www.wannyan.net/scidog/ellipse/ellipse.pdf
//-----------------------------------------------------
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)
    LabeledEdit10: TLabeledEdit;
    LabeledEdit11: TLabeledEdit;
    Memo1: TMemo;
    nawaBtn: TButton;
    Chart1: TChart;
    Series1: TLineSeries;
    Series2: TPointSeries;
    Xpoint_Edit: TLabeledEdit;
    Ypoint_Edit: TLabeledEdit;
    LabeledEdit1: TLabeledEdit;
    CheckBox1: TCheckBox;
    Label1: TLabel;
    Series3: TPointSeries;
    Series4: TLineSeries;
    Series5: TLineSeries;
    procedure nawaBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure CheckBox1KeyPress(Sender: TObject; var Key: Char);
  private
    { Private 宣言 }
    function jacobi_sn(u, k: double): double;
    function invertK(L, a2: double): double;
    procedure tangent(a, b, c, k, xp, yp: double);
    procedure Linedraw(a0, a1, c0, c1: double; RF, LF: boolean);
    procedure Complete_Elliptic_Integrals(k: double; var Fk, Ek: double);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses Math;


var
  Ns : integer;
  DBL_EPSILON : double;

// 第一二種完全楕円積分
// K 離心率
// Fk 第一種
// Ek 第二種
procedure TForm1.Complete_Elliptic_Integrals(k: double; var Fk, Ek: double);
const
  KN = 30;
var
  m     : double;     // the parameter of the elliptic function m = k^2
  a     : double;     // arithmetic mean
  g     : double;     // geometric mean
  a_old : double;     // previous arithmetic mean
  g_old : double;     // previous geometric mean
  two_n : integer;    // power of 2
  sum   : double;
  half  : double;
  N     : integer;    // loop数
begin
  half := 1 / 2;
  if k = 0 then begin
    Fk := PI / 2;
    Ek := Fk;
    exit;
  end;
  m := k * k;
  if m = 1 then begin
    Fk := infinity;
    Ek := 1;
    exit;
  end;
  a := 1;
  g := sqrt(1 - m);
  two_n := 1;
  sum := (2 - m);
  N := 0;
  while N < KN do begin     // 収束しなかった時KN回で終了
    g_old := g;
    a_old := a;
    a := (g_old + a_old) * half;
    g := sqrt(g_old * a_old);
    two_n := two_n + two_n;
    sum := sum - two_n * (a * a - g * g);
    if abs(a_old - g_old) <= a_old * DBL_EPSILON then break;
    inc(N);
  end;
  Fk := PI / 2 / a;
  Ek := PI / 4 / a * sum;
end;

// Jacobi関数 Jacobi sn(u, k)
function TForm1.Jacobi_sn(u, k: double): double;
const
  KN = 30;
var
  a: array of double;
  g: array of double;
  c: array of double;
  two_n   : integer;
  phi     : double;
  half    : double;
  i, j, N : integer;
begin
  half := 1 / 2;
  if k = 0 then begin
    result := sin(u);
    exit;
  end;
  if k = 1 then begin
    result := sin(2 * arctan(exp(u)) - pi / 2);
    exit;
  end;
  N := 1;
  setlength(a, N);
  setlength(g, N);
  setlength(c, N);
  a[0] := 1;
  g[0] := sqrt(1 - k * k);
  c[0] := k;
  two_n := 1;
  for i := 0 to KN do begin         // 収束しなかった時KNで終了
    if abs(a[i] - g[i]) < a[i] * DBL_EPSILON then break;
    two_n := two_n + two_n;
    inc(N);
    setlength(a, N);
    setlength(g, N);
    setlength(c, N);
    a[i + 1] := half * (a[i] + g[i]);
    g[i + 1] := sqrt(a[i] * g[i]);
    c[i + 1] := half * (a[i] - g[i]);
  end;
  Ns := i;
  phi := two_n * a[i] * u;
  for j := i downto 1 do
    phi := half * (phi + arcsin(c[j] * sin(phi) / a[j]));
  result := sin(phi);
end;

// 離芯率kの計算
// L=4a/k'^2*E/K-2aのaとLが既知なのでkの値を求めます。
function TForm1.invertK(L, a2: double): double;
const
  KN = 500;
var
  c, fc     : double;
  k, E, Fk  : double;
  dk        : double;
  N         : integer;
begin
  a2 := abs(a2);
  c := (L + a2) / a2 / 2;                     // L=4a/k'^2*E/K-2a から判定用固定値計算
  k := 0;                                     // 離心率初期値
  dk := 1 / 2;                                // Δk
  N := 0;
  repeat
    inc(N);
    k := k + dk;                              // k+Δk
    if k >= 1 then k := 1 - DBL_EPSILON;      // kが1に成った時1から僅かに小さくします。
    Complete_Elliptic_Integrals(k, Fk, E);
    fc := E / Fk / (1 - k * k);               // 判定比較値計算
    if fc > c then begin                      // 大きかったら
      k := k - dk;                            // Δk減算
      dk := dk / 2;                           // Δk 2分の1
    end;
  until (dk < DBL_EPSILON * k) or (N > KN);  // 収束判定
  memo1.Lines.Add('離芯率計算ループ数 N = ' + intTostr(N));
  result := k;
end;

// 接線の描画
// a0, a1 接線の傾斜
// c0, c1 接線の切片
procedure TForm1.Linedraw(a0, a1, c0, c1: double; RF, LF: boolean);
const
  KN = 100;
var
  t, b, l, r: double;
  w         : double;
  x, dx, y  : double;
  i         : integer;
begin
  if not RF and not LF then exit;
  t := Series2.YValue[0];       // グラフ上限
  b := Series2.YValue[1];       // グラフ下限
  l := Series2.XValue[1];       // グラフ左限
  r := Series2.XValue[2];       // グラフ右限
  w := r - l;                   // 幅
  dx := w / KN;                 // 描画間隔
  for i := 0 to KN do begin
    x := i * dx + l;
    if LF then begin
      y := a0 * x + c0;
      if (y < t) and (y > b) then Series4.AddXY(x, y);
    end;
    if RF then begin
      y := a1 * x + c1;
      if (y < t) and (y > b) then Series5.AddXY(x, y);
    end;
  end;
end;

// 接線の計算
// a 縄跳びの間隔の二分の一 90°分
// b 紐の高さ
// c 紐の係数 (端点の傾斜b/c)
// xp, yp 通過点の座標
procedure TForm1.tangent(a, b, c, k, xp, yp: double);
const
  KN = 100;
var
  alpha     : double;                                 // 端点傾斜角
  La, Ra    : double;                                 // 傾斜
  LF, RF    : boolean;                                // 接線有無フラグ
  yc, u     : double;
  snuk      : double;
  sn2uk     : double;
  x, y, dx  : double;
  N         : integer;
  Salpha    : double;                                 // 直線の傾斜
  a0, a1    : double;                                 // 接線の傾斜
  c0, c1    : double;                                 // 接線の切片
begin
  // 接点があるか確認
  alpha := b / c;
  yc := 0;
  if (xp > -a) and (a > xp) then begin                // xpの座標が縄間隔内なら yc計算
    u := (xp + a) / c;                                // F(α,k)=u  u ゼロ基準に(x + a)
    snuk := jacobi_sn(u, k);                          // jacobi_sn  ヤコビの楕円関数
    yc := snuk * b;                                   // x位置のyの値
  end;
  La := MaxDouble;                                    // 最大値セット∞の代わり
  Ra := -La;                                          // 最小値セット-∞の代わり
  LF := False;
  RF := False;
  if yp >= yc then begin                              // ycよりypが大きい場合
    if xp <> -a then La := yp / (xp + a);             // 左端点の傾斜計算
    if xp <> a  then Ra := yp / (xp - a);             // 右端点の傾斜計算
    if (La <  alpha) and (Xp > -a) then LF := True;   // 左の傾斜が端点の傾斜より小さい場合True
    if (Ra > -alpha) and (Xp <  a) then RF := True;   // 右の傾斜が端点の傾斜より小さい場合True
  end;
  if (yp < 0) then begin                              // ypがゼロより小さい場合
    if xp < -a then La := yp / (xp + a);              // 左端点の傾斜計算
    if xp > a  then Ra := yp / (xp - a);              // 右端点の傾斜計算
    if (La <  alpha) and (Xp < -a) then RF := True;   // 左端点の傾斜が小さい場合右True
    if (Ra > -alpha) and (Xp >  a) then LF := True;   // 右端点の傾斜が小さい場合左True
  end;
  // 接点があったら接点位置の計算
  dx := a * 2 / KN;                                   // Δx=a2/KN
  N := 0;
  x := 0;
  a0 := 0;
  if LF then begin                                    // 左端から接線位置検索
    repeat
      x := x + dx;                                    // x位置
      u := x / c;                                     // F(α,k)=u
      snuk := jacobi_sn(u, k);                        // jacobi_sn  ヤコビの楕円関数
      y := snuk * b;                                  // x位置のyの値
      sn2uk := snuk * snuk;
      alpha := b / c * sqrt((1 - sn2uk) * (1 - k * K * sn2uk)); // 紐曲線の傾き
      Salpha := (yp - y) / (xp - (x - a ));           // 直線の傾き
      if x > a then alpha := -alpha;                  // 微分値がプラスのみなのでXの値で-設定
      if Salpha > alpha then begin                    // 直線の傾斜が曲線の傾斜より大きくなったら
        x := x - dx;                                  // xの値戻し
        dx := dx / 2;                                 // dx二分の一
        N := 0;                                       // 加算回数クリア
      end
      else inc(N);
    until (N > KN) or (dx < DBL_EPSILON * x);
    Series3.AddXY(x - a, y);                          // 通過指定点
    a0 := Salpha;                                     // 接線1の傾斜
  end;
  dx := a * 2 / KN;                                   // Δx=a2/KN
  N := 0;
  x := 0;
  a1 := 0;
  if RF then begin                                    // 右端から接線位置検索
    repeat
      x := x + dx;                                    // x位置
      u := (a + a - x) / c;                           // F(α,k)=u
      snuk := jacobi_sn(u, k);                        // jacobi_sn  ヤコビの楕円関数
      y := snuk * b;                                  // x位置のyの値
      sn2uk := snuk * snuk;
      alpha := b / c * sqrt((1 - sn2uk) * (1 - k * K * sn2uk)); // 紐曲線の傾き
      Salpha := (yp - y) / (xp - (a - x));            // 直線の傾き
      if (a + a - x) > a then alpha := -alpha;        // 微分値がプラスのみなのでXの値で-設定
      if Salpha < alpha then begin                    // 直線の傾斜が曲線の傾斜より小さくなったら
        x := x - dx;                                  // xの値戻し
        dx := dx / 2;                                 // dx二分の一
        N := 0;                                       // 加算回数クリア
      end
      else inc(N);                                    // 加算回数
    until (N > KN) or (dx < DBL_EPSILON * x);         // 加算回数が上限を超える又はdxが指定値より小さくなったら
    Series3.AddXY((a - x), y);                        // 通過指定点
    a1 := Salpha;                                     // 接線2の傾斜
  end;
  c0 := 0;
  if (abs(xp) = a) and (yp = 0) then begin            // 指定点が縄端点なら
    if xp < 0 then begin
      a0 := alpha;
      LF := True;
    end
    else begin
      a1 := -alpha;
      RF := True;
    end;
  end;
  if LF then memo1.Lines.Add('接線計算開始点 左');
  if RF then memo1.Lines.Add('接線計算開始点 右');
  if not LF and not RF then memo1.Lines.Add('接線無し');
  if LF then begin
    c0 := yp - a0 * xp;                               // 切片の計算
    memo1.Lines.Add('y=' + floatTostrF(a0,fffixed, 10,5) + 'x + ' + floatTostrF(c0,fffixed, 10,5));
  end;
  c1 := 0;
  if RF then begin
    c1 := yp - a1 * xp;                               // 切片の計算
    memo1.Lines.Add('y=' + floatTostrF(a1,fffixed, 10,5) + 'x + ' + floatTostrF(c1,fffixed, 10,5));
  end;
  Linedraw(a0, a1, c0, c1, RF, LF);                   // 接線の描画
end;

// 縄跳びの縄弛み計算
procedure TForm1.nawaBtnClick(Sender: TObject);
const
  kN  = 400;
var
  k         : double;
  ch        : integer;
  a, a2     : double;
  b, u      : double;
  c, L      : double;
  i         : integer;
  x, y, dx  : double;
  snuk      : double;
  ty, by    : double;
  lx, rx    : double;
  def, ddf  : double;
  Ek, Fk    : double;
  Lk        : double;
  xp, yp    : double;
  xmax, xmin : double;
  ymax, ymin : double;
begin
  Ns := 0;                    // sn(u,k)ループ数
  k := 0;                     // 離心率
  L := 0;                     // 紐長さ
  val(Labelededit10.Text, a2, ch);
  if ch <> 0 then begin
    application.MessageBox('縄跳び紐支持間に間違いがあります。','注意',0);
    exit;
  end;
  if a2 <= 0 then begin
    application.MessageBox('縄跳び紐支持間はゼロ以上にして下さい。','注意',0);
    exit;
  end;
  val(Xpoint_Edit.Text, xp, ch);
  if ch <> 0 then begin
    application.MessageBox('X座標の値に間違いがあります。','注意',0);
    exit;
  end;
  val(Ypoint_Edit.Text, yp, ch);
  if ch <> 0 then begin
    application.MessageBox('Y座標の値に間違いがあります。','注意',0);
    exit;
  end;
  if checkbox1.Checked then begin
    val(LabeledEdit1.Text, k, ch);
    if ch <> 0 then begin
      application.MessageBox('縄跳び紐離心率Kに間違いがあります。','注意',0);
      exit;
    end;
    if k > 1 then begin
      application.MessageBox('k(離心率)の最大値は1です。','注意',0);
      exit;
    end;
    if k < 0 then begin
      application.MessageBox('k(離心率)の値にマイナスはありません。','注意',0);
      exit;
    end;
  end
  else begin
    val(LabeledEdit11.Text, L, ch);
    if ch <> 0 then begin
      application.MessageBox('縄跳び紐の長さに間違いがあります。','注意',0);
      exit;
    end;
    if L <= abs(a2) then begin
      application.MessageBox('紐の長さは支持間より長くしてください。','注意',0);
      exit;
    end;
  end;
  memo1.Clear;
  if not checkbox1.Checked then begin
    memo1.Lines.Add('紐の長さ(L=' + floatTostr(L) + ')指定計算');
    k := invertK(L, a2);                                    // 離芯率kの計算
    memo1.Lines.Add('離芯率          k = ' + floatTostrF(k, ffFixed, 10, 5));
  end
  else
    memo1.Lines.Add('離心率(k=' + floatTostr(k) + ')指定計算');
  Complete_Elliptic_Integrals(k, Fk, Ek);                   // 第一二種完全積分
  if abs(k) = 1 then begin
    application.MessageBox('離心率の絶対値が1です1以下にして下さい。','注意',0);
    exit;
  end;
  if abs(k) = 0 then begin
    application.MessageBox('離心率がゼロです。','注意',0);
    exit;
  end;
  // 各種計算
  a := a2 / 2;
  c := a / Fk;                                              // 積分値に対する曲線係数
  b := 2 * k / (1 - k * k) * c;                             // 最大値 中央の位置
  memo1.Lines.Add('曲線係数        c = ' + floatTostrF(c, ffFixed, 10, 5));
  memo1.Lines.Add('紐の中心高さ      b = ' + floatTostrF(b, ffFixed, 10, 5));
  // 紐の長さ計算 中間位置の計算は出来ません
  if checkbox1.Checked then begin
    Complete_Elliptic_Integrals(k, Fk, Ek);                 // 第一二種完全積分
    Lk := 4 * c * Ek / (1 - k * k) - a2;                    // 弧の長さ計算
    memo1.Lines.Add('紐の長さ        L = ' + floatTostrF(Lk, ffFixed, 10, 5));
  end;
  Series1.Clear;
  Series2.Clear;
  Series3.Clear;
  Series4.Clear;
  Series5.Clear;
  xmax := a;
  xmin := -a;
  ymax := b;
  ymin := 0;
  if xmax < xp then xmax := xp;
  if xmin > xp then xmin := xp;
  if ymax < yp then ymax := yp;
  if ymin > yp then ymin := yp;

  // グラフスケール設定
  if (xmax - xmin) > (ymax - ymin) then begin
    ddf := (xmax - xmin) / 10;
    def := ((xmax - xmin) - (ymax - ymin)) / 2;
    ty := ymax + def + ddf;
    by := ymin - def - ddf;
    rx := xmax + ddf;
    lx := xmin - ddf;
  end
  else begin
    ddf := (ymax - ymin) / 10;
    def := ((ymax - ymin) - (xmax - xmin)) / 2;
    ty := ymax + ddf;
    by := ymin - ddf;
    rx := xmax + def + ddf;
    lx := xmin - def - ddf;
  end;
  // グラフのスケール設定値セット
  Series2.AddXY(lx, ty);
  Series2.AddXY(lx, by);
  Series2.AddXY(rx, by);
  application.ProcessMessages;                      // グラフ表示待ち
  // グラフ作図
  dx := a / KN;                                     // ΔX
  // 0~KN 計算作図
  for i := -KN to KN do begin
    x := i * dx;                                    // x位置
    u := (x + a) / c;                               // F(α,k)=u  u ゼロ基準に(x + a)
    snuk := jacobi_sn(u, k);                        // jacobi_sn  ヤコビの楕円関数
    y := snuk * b;                                  // x位置のyの値
    Series1.AddXY(x, y);                            // グラフに追加
  end;
  Series3.AddXY(xp, yp);                            // 通過指定点
  application.ProcessMessages;                      // グラフ表示待ち
  tangent(a, b, c, k, xp, yp);                      // 接線の計算
  Label1.Caption:= 'sn(u) 最大Loop数= ' + intTostr(Ns);
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if CheckBox1.Checked then begin
    labeledEdit1.Enabled := True;
    labeledEdit11.Enabled := False;
  end
  else begin
    labeledEdit1.Enabled := False;
    labeledEdit11.Enabled := True;
  end;
end;

procedure TForm1.CheckBox1KeyPress(Sender: TObject; var Key: Char);
begin
  if CheckBox1.Checked then CheckBox1.Checked := False
                       else CheckBox1.Checked := True;
end;

// 初期設定
// DBL_EPSILON 桁落ち値の設定 1 + y = 1 のyの値検出 y = 1E-i
// DBL_EPSILON = y * 10;
procedure TForm1.FormCreate(Sender: TObject);
const
  IM = 50;
var
  x, y, z : double;
  i       : integer;
begin
  labeledEdit1.Enabled := False;
  x := 1;
  y := 1 / 4194304;               // 4194304 = 2^22
  repeat
    y := y / 2;
    z := x + y;
  until (z = 1) or (i > IM);
  DBL_EPSILON := y * 2;
end;

end.

    download jacobi_elliptic_specified_point_Passing_straight_line_tangent_to_a_curve.zip

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

      最初に戻る