1

微分に関する私の質問に続いて:

Delphi によるバッファの微分

現在、統合を検討しています。私はこれについて頭を悩ませることはできません。状況は、時間的に一定の距離にある多数の値を含むデータのバッファーを定期的に受信することです。それらを区別する必要があります。学校で微積分をやるのは久しぶりです....

私が思いついたのはこれです:

procedure IntegrateBuffer(ABuffer: TDoubleDynArray;
                                   var AOutBuffer: TDoubleDynArray;
                                   AVPS: integer);
const
  SumSum: double = 0.0;
  LastValue: double = NaN;
var
  i: integer;
  dt, aa, hl, hr: double;
begin
  // protect from divide by zero
  if (AVPS < 1) then exit;

  dt := 1 / AVPS;

  for i := 0 to high(ABuffer) do begin
    if (i = 0) then begin
      if (IsNaN(LastValue)) then begin
        hl := ABuffer[0];
        hr := ABuffer[0];
      end else begin
        hl := LastValue;
        hr := ABuffer[i];
      end;
    end else begin
      hl := ABuffer[i -1];
      hr := ABuffer[i];
    end;

    aa := 0.5 * dt * (hl + hr);
    SumSum := SumSum + aa;
    AOutBuffer[i] := SumSum;
  end;

  // remember the last value for next time
  LastValue := ABuffer[high(ABuffer)];
end;

私は台形定規を使用しています。hl と hr は台形の左右の高さです。dt がベースです。

AVPS は 1 秒あたりの値です。これの典型的な値は 10 から 100 の間です。バッファの長さは、典型的には 500 から 1000 の値です。

前のデータブロックと連続する新しいデータでバッファを何度も呼び出すため、次回のためにブロックの最後の値を保持します。

私がしたことは正しいですか?つまり、値を適切に統合しますか?

ありがとうございました。

4

1 に答える 1

7

コードのテストに助けが必要なようです。ここでは、コメントで説明されているように、非常に単純なテストです。

{$APPTYPE CONSOLE}

uses
  SysUtils, Math;

type
  TDoubleDynArray = array of Double;

var
  SumSum: double;
  LastValue: double;

procedure Clear;
begin
  SumSum := 0.0;
  LastValue := NaN;
end;

procedure IntegrateBuffer(
  ABuffer: TDoubleDynArray;
  var AOutBuffer: TDoubleDynArray;
  AVPS: integer
);
var
  i: integer;
  dt, aa, hl, hr: double;
begin
  // protect from divide by zero
  if (AVPS < 1) then exit;

  dt := 1 / AVPS;

  for i := 0 to high(ABuffer) do begin
    if (i = 0) then begin
      if (IsNaN(LastValue)) then begin
        hl := ABuffer[0];
        hr := ABuffer[0];
      end else begin
        hl := LastValue;
        hr := ABuffer[i];
      end;
    end else begin
      hl := ABuffer[i -1];
      hr := ABuffer[i];
    end;

    aa := 0.5 * dt * (hl + hr);
    SumSum := SumSum + aa;
    AOutBuffer[i] := SumSum;
  end;

  // remember the last value for next time
  LastValue := ABuffer[high(ABuffer)];
end;

var
  Buffer: TDoubleDynArray;
  OutBuffer: TDoubleDynArray;

begin
  // test y = 1 for a single call, expected output = 1, actual output = 2
  Clear;
  Buffer := TDoubleDynArray.Create(1.0, 1.0);
  SetLength(OutBuffer, Length(Buffer));
  IntegrateBuffer(Buffer, OutBuffer, 1);
  Writeln(OutBuffer[high(OutBuffer)]);

  Readln;
end.

y(x) = 1[0..1] の範囲で関数を統合しています。したがって、期待される出力は 1 ですが、実際の出力は 2 です。

それで、何が問題なのですか?デバッガーで解決できますが、コードを調べれば簡単に確認できます。最初のサンプルで三角形を合計しています。が真の場合IsNaN(LastValue)、積分に寄与するべきではありません。その時点で、x 軸上の距離をカバーしていません。

コードを修正するために、これを試してみましょう。

....
if (IsNaN(LastValue)) then begin
  hl := 0.0;//no contribution to sum
  hr := 0.0;
end else begin
  hl := LastValue;
  hr := ABuffer[i];
end;
....

これで問題は解決しました。

それでは、テストを少し拡張してテストしましょうy(x) = x:

// test y = x, expected output = 12.5
Clear;
Buffer := TDoubleDynArray.Create(0.0, 1.0, 2.0, 3.0, 4.0, 5.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Writeln(OutBuffer[high(OutBuffer)]);

それで、それはよさそうです。

OK、複数の呼び出しはどうですか:

// test y = x for multiple calls, expected output = 18
Clear;
Buffer := TDoubleDynArray.Create(0.0, 1.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Buffer := TDoubleDynArray.Create(2.0, 3.0, 4.0, 5.0, 6.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Writeln(OutBuffer[high(OutBuffer)]);

一度に 1 つの値についてはどうでしょうか。

// test y = x for multiple calls, one value at a time, expected 0.5
Clear;
Buffer := TDoubleDynArray.Create(0.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Buffer := TDoubleDynArray.Create(1.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Writeln(OutBuffer[high(OutBuffer)]);

空の配列を渡すとどうなりますか?

// test y = x for multiple calls, some empty arrays, expected 0.5
Clear;
Buffer := TDoubleDynArray.Create(0.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Buffer := nil;
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Buffer := TDoubleDynArray.Create(1.0);
SetLength(OutBuffer, Length(Buffer));
IntegrateBuffer(Buffer, OutBuffer, 1);
Writeln(OutBuffer[high(OutBuffer)]);

アクセス違反です。バッファが空の場合、最初に関数をスキップするだけで、より適切に保護できます。

if (AVPS < 1) then exit;
if (Length(ABuffer) = 0) then exit;

OK、これで最後のテストに合格しました

うまくいけば、あなたは今アイデアを得るでしょう。noddyWritelnベースのテストを使用しましたが、スケーリングしません。単体テスト フレームワーク (DUnitX をお勧めします) を用意し、適切なテスト ケースを作成します。これにより、適切に設計されるようにコードを因数分解することも強制されます。コードをテスト可能にすることの予想外の利点の 1 つは、通常、インターフェイスの設計が改善されることです。

次の質問ですが、SSCCEにテスト コードを添付してください。;-)


コードに関するいくつかのコメント:

  1. constまたは によって動的配列を渡しますvar。あなたの場合、入力バッファをconst.
  2. 書き込み可能な型付き定数は使用しないでください。パラメータを使用するか、他のより適切な状態管理を使用してください。

繰り返しますが、前の質問で述べたように、コードを証明するテストを作成し、目で確認します。テストを書く上で重要なことは、考えられる最も単純なことから始めることです。答えが 100% 確実にわかるほど単純なこと。次に、それが機能するようになったら、テストをより複雑なケースに拡張します。

于 2013-10-04T10:19:00.907 に答える