From 6952568642c56a3cdb9e24c6e5fafea5c304a818 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Tue, 5 May 2020 22:57:07 +0000 Subject: [PATCH] LazStats: Fix operation of MoveAvgUnit needed by AutoCorUnit. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7430 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../analysis/correlation/autocorunit.lfm | 1 - .../analysis/correlation/autocorunit.pas | 45 ++-- .../analysis/correlation/moveavgunit.lfm | 148 +++++------ .../analysis/correlation/moveavgunit.pas | 249 ++++++++++-------- 4 files changed, 232 insertions(+), 211 deletions(-) diff --git a/applications/lazstats/source/forms/analysis/correlation/autocorunit.lfm b/applications/lazstats/source/forms/analysis/correlation/autocorunit.lfm index bff19404e..f51d2af73 100644 --- a/applications/lazstats/source/forms/analysis/correlation/autocorunit.lfm +++ b/applications/lazstats/source/forms/analysis/correlation/autocorunit.lfm @@ -9,7 +9,6 @@ object AutoCorrFrm: TAutoCorrFrm ClientWidth = 684 OnActivate = FormActivate OnCreate = FormCreate - OnShow = FormShow Position = poMainFormCenter LCLVersion = '2.1.0.0' object GroupBox1: TGroupBox diff --git a/applications/lazstats/source/forms/analysis/correlation/autocorunit.pas b/applications/lazstats/source/forms/analysis/correlation/autocorunit.pas index e5e7d2d3b..53bbe2266 100644 --- a/applications/lazstats/source/forms/analysis/correlation/autocorunit.pas +++ b/applications/lazstats/source/forms/analysis/correlation/autocorunit.pas @@ -72,7 +72,6 @@ type procedure ComputeBtnClick(Sender: TObject); procedure FormActivate(Sender: TObject); procedure FormCreate(Sender: TObject); - procedure FormShow(Sender: TObject); procedure HelpBtnClick(Sender: TObject); procedure InBtnClick(Sender: TObject); procedure OutBtnClick(Sender: TObject); @@ -88,6 +87,8 @@ type private FExpSmoothAlpha: Double; + FMovAvgRawWeights: DblDyneVec; + FMovAvgOrder: Integer; function CalcMean(const pts: DblDyneVec; NoPts: Integer): Double; procedure ExponentialSmooth(var Pts: DblDyneVec; NoPts: Integer); procedure Four1(var data: DblDyneVec; nn: LongWord; isign: integer); @@ -149,7 +150,11 @@ begin if IsFiltered(i) then continue; VarList.Items.Add(OS3MainFrm.DataGrid.Cells[0,i]); end; + + FMovAvgOrder := 1; + FMovAvgRawWeights := nil; FExpSmoothAlpha := 0.99; + UpdateBtnStates; end; @@ -198,10 +203,6 @@ begin Assert(OS3MainFrm <> nil); if PointsFrm = nil then Application.CreateForm(TPointsFrm, PointsFrm); FExpSmoothAlpha := 0.99; -end; - -procedure TAutoCorrFrm.FormShow(Sender: TObject); -begin ResetBtnClick(self); end; @@ -1241,7 +1242,6 @@ procedure TAutoCorrFrm.MovingAverage(var Pts: DblDyneVec; NoPts: Integer); var F: TMoveAvgFrm; i, j: Integer; - nValues: Integer; avg: DblDyneVec; residual: DblDyneVec; noProj: Integer; @@ -1250,10 +1250,14 @@ var begin F := TMoveAvgFrm.Create(nil); try + F.Order := FMovAvgOrder; + F.RawWeights := FMovAvgRawWeights; if F.ShowModal <> mrOK then exit; - nValues := F.Order; - if nValues <= 0 then + + FMovAvgRawWeights := F.RawWeights; + FMovAvgOrder := F.Order; + if FMovAvgOrder <= 0 then exit; if ProjectChk.Checked then @@ -1263,26 +1267,25 @@ begin // Calculate moving average SetLength(avg, NoPts + NoProj); - for i := nValues to NoPts - nValues - 1 do + for i := F.Order to NoPts - F.Order - 1 do begin - avg[i] := Pts[i] * F.W[0]; // middle value - for j := 1 to nValues do - avg[i] := avg[i] + Pts[i-j] * F.W[j] + Pts[i+j] * F.W[j]; - // left values right values + avg[i] := Pts[i] * F.Weights[0]; // middle value + for j := 1 to F.Order do + avg[i] := avg[i] + (Pts[i-j] + Pts[i+j]) * F.Weights[j]; end; // fill in unestimable averages with original points - for i := 0 to nValues - 1 do // left values + for i := 0 to F.Order - 1 do // left values begin - avg[i] := pts[i] * F.W[0]; - for j := 1 to nvalues do - avg[i] := avg[i] + (pts[i+j] * 2.0 * F.W[j]); + avg[i] := pts[i] * F.Weights[0]; + for j := 1 to F.Order do + avg[i] := avg[i] + pts[i+j] * 2.0 * F.Weights[j]; end; - for i := NoPts - nValues to NoPts - 1 do //right values + for i := NoPts - F.Order to NoPts - 1 do //right values begin - avg[i] := pts[i] * F.W[0]; - for j := 1 to nvalues do - avg[i] := avg[i] + (pts[i-j] * 2.0 * F.W[j]); + avg[i] := pts[i] * F.Weights[0]; + for j := 1 to F.Order do + avg[i] := avg[i] + (pts[i-j] * 2.0 * F.Weights[j]); end; if ProjectChk.Checked then diff --git a/applications/lazstats/source/forms/analysis/correlation/moveavgunit.lfm b/applications/lazstats/source/forms/analysis/correlation/moveavgunit.lfm index a2b02ac03..721c16433 100644 --- a/applications/lazstats/source/forms/analysis/correlation/moveavgunit.lfm +++ b/applications/lazstats/source/forms/analysis/correlation/moveavgunit.lfm @@ -1,84 +1,42 @@ object MoveAvgFrm: TMoveAvgFrm Left = 434 - Height = 292 + Height = 298 Top = 163 Width = 372 Caption = 'Moving Average Specification Form' - ClientHeight = 292 + ClientHeight = 298 ClientWidth = 372 OnActivate = FormActivate - OnShow = FormShow + OnCreate = FormCreate Position = poMainFormCenter LCLVersion = '2.1.0.0' object Label1: TLabel + AnchorSideLeft.Control = Owner AnchorSideTop.Control = OrderEdit AnchorSideTop.Side = asrCenter AnchorSideRight.Control = OrderEdit - Left = 38 + Left = 8 Height = 15 Top = 17 Width = 36 - Anchors = [akTop, akRight] + BorderSpacing.Left = 8 BorderSpacing.Right = 8 Caption = 'Order: ' ParentColor = False end - object Label2: TLabel - AnchorSideLeft.Control = Owner - AnchorSideTop.Control = ThetaEdit - AnchorSideTop.Side = asrCenter - Left = 8 - Height = 15 - Top = 52 - Width = 66 - BorderSpacing.Left = 8 - Caption = 'Theta Value: ' - ParentColor = False - end object OrderEdit: TEdit - AnchorSideLeft.Control = ThetaEdit - AnchorSideRight.Control = ThetaList + AnchorSideLeft.Control = Label1 + AnchorSideLeft.Side = asrBottom AnchorSideRight.Side = asrBottom - Left = 82 + Left = 52 Height = 23 Top = 13 - Width = 118 + Width = 102 Alignment = taRightJustify OnEditingDone = OrderEditEditingDone TabOrder = 0 Text = 'OrderEdit' end - object ThetaEdit: TEdit - AnchorSideLeft.Control = Label2 - AnchorSideLeft.Side = asrBottom - AnchorSideRight.Control = ThetaList - AnchorSideRight.Side = asrBottom - Left = 82 - Height = 23 - Top = 48 - Width = 118 - Alignment = taRightJustify - BorderSpacing.Left = 8 - TabOrder = 1 - Text = 'ThetaEdit' - end - object ThetaList: TListBox - AnchorSideLeft.Control = Owner - AnchorSideRight.Control = Owner - AnchorSideRight.Side = asrBottom - AnchorSideBottom.Control = OKBtn - Left = 8 - Height = 171 - Top = 80 - Width = 356 - Anchors = [akTop, akLeft, akRight, akBottom] - BorderSpacing.Left = 8 - BorderSpacing.Right = 8 - BorderSpacing.Bottom = 8 - ItemHeight = 0 - OnClick = ThetaListClick - TabOrder = 2 - end object OKBtn: TButton AnchorSideRight.Control = Owner AnchorSideRight.Side = asrBottom @@ -86,23 +44,24 @@ object MoveAvgFrm: TMoveAvgFrm AnchorSideBottom.Side = asrBottom Left = 322 Height = 25 - Top = 259 + Top = 265 Width = 42 Anchors = [akRight, akBottom] AutoSize = True + BorderSpacing.Top = 8 BorderSpacing.Right = 8 BorderSpacing.Bottom = 8 Caption = 'OK' ModalResult = 1 OnClick = OKBtnClick - TabOrder = 3 + TabOrder = 5 end object CancelBtn: TButton AnchorSideTop.Control = OKBtn AnchorSideRight.Control = OKBtn Left = 252 Height = 25 - Top = 259 + Top = 265 Width = 62 Anchors = [akTop, akRight] AutoSize = True @@ -111,47 +70,84 @@ object MoveAvgFrm: TMoveAvgFrm ModalResult = 2 TabOrder = 4 end - object ApplyBtn: TButton - AnchorSideTop.Control = OKBtn - AnchorSideRight.Control = CancelBtn - Left = 187 - Height = 25 - Top = 259 - Width = 57 - Anchors = [akTop, akRight] - AutoSize = True - BorderSpacing.Right = 8 - Caption = 'Apply' - OnClick = ApplyBtnClick - TabOrder = 5 - end object ResetBtn: TButton AnchorSideTop.Control = OKBtn - AnchorSideRight.Control = ApplyBtn - Left = 125 + AnchorSideRight.Control = CancelBtn + Left = 190 Height = 25 - Top = 259 + Top = 265 Width = 54 Anchors = [akTop, akRight] AutoSize = True BorderSpacing.Right = 8 Caption = 'Reset' OnClick = ResetBtnClick - TabOrder = 6 + TabOrder = 3 end object HelpBtn: TButton Tag = 132 AnchorSideTop.Control = OKBtn AnchorSideRight.Control = ResetBtn - Left = 66 + Left = 131 Height = 25 - Top = 259 + Top = 265 Width = 51 Anchors = [akTop, akRight] AutoSize = True BorderSpacing.Right = 8 Caption = 'Help' OnClick = HelpBtnClick - TabOrder = 7 + TabOrder = 2 + end + object Bevel1: TBevel + AnchorSideLeft.Control = Owner + AnchorSideRight.Control = Owner + AnchorSideRight.Side = asrBottom + AnchorSideBottom.Control = OKBtn + Left = 0 + Height = 8 + Top = 249 + Width = 372 + Anchors = [akLeft, akRight, akBottom] + Shape = bsBottomLine + end + object WeightGrid: TStringGrid + AnchorSideLeft.Control = Owner + AnchorSideTop.Control = OrderEdit + AnchorSideTop.Side = asrBottom + AnchorSideRight.Control = Owner + AnchorSideRight.Side = asrBottom + AnchorSideBottom.Control = Bevel1 + Left = 8 + Height = 205 + Top = 44 + Width = 356 + Anchors = [akTop, akLeft, akBottom] + AutoFillColumns = True + BorderSpacing.Left = 8 + BorderSpacing.Top = 8 + BorderSpacing.Right = 8 + ColCount = 3 + Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goDrawFocusSelected, goEditing, goThumbTracking, goSmoothScroll, goFixedColSizing] + TabOrder = 1 + OnEditingDone = WeightGridEditingDone + OnSelectEditor = WeightGridSelectEditor + ColWidths = ( + 78 + 137 + 137 + ) + Cells = ( + 3 + 0 + 0 + 'Weight #' + 1 + 0 + 'Weight Value' + 2 + 0 + 'Normalized' + ) end end diff --git a/applications/lazstats/source/forms/analysis/correlation/moveavgunit.pas b/applications/lazstats/source/forms/analysis/correlation/moveavgunit.pas index b52252d86..5bf005c9b 100644 --- a/applications/lazstats/source/forms/analysis/correlation/moveavgunit.pas +++ b/applications/lazstats/source/forms/analysis/correlation/moveavgunit.pas @@ -6,41 +6,46 @@ interface uses Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, - StdCtrls, ExtCtrls, - ContextHelpUnit; + StdCtrls, ExtCtrls, Grids, + Globals, ContextHelpUnit; type { TMoveAvgFrm } TMoveAvgFrm = class(TForm) + Bevel1: TBevel; HelpBtn: TButton; ResetBtn: TButton; CancelBtn: TButton; - ApplyBtn: TButton; OKBtn: TButton; - ThetaList: TListBox; - ThetaEdit: TEdit; - Label2: TLabel; OrderEdit: TEdit; Label1: TLabel; - procedure ApplyBtnClick(Sender: TObject); + WeightGrid: TStringGrid; procedure FormActivate(Sender: TObject); - procedure FormShow(Sender: TObject); + procedure FormCreate(Sender: TObject); procedure HelpBtnClick(Sender: TObject); procedure OKBtnClick(Sender: TObject); procedure OrderEditEditingDone(Sender: TObject); procedure ResetBtnClick(Sender: TObject); - procedure ThetaEditEditingDone(Sender: TObject); - procedure ThetaListClick(Sender: TObject); + procedure WeightGridEditingDone(Sender: TObject); + procedure WeightGridSelectEditor(Sender: TObject; aCol, aRow: Integer; + var Editor: TWinControl); private { private declarations } + FOrder: Integer; + FWeights: DblDyneVec; + FRawWeights: DblDyneVec; + function GetRawWeights: DblDyneVec; + procedure NormalizeWeights; + procedure SetOrder(AValue: Integer); + procedure SetRawWeights(const AValue: DblDyneVec); function Validate(out AMsg: String; out AControl: TWinControl): Boolean; public { public declarations } - W: array[0..20] of double; - order : integer; - currentindex : integer; + property RawWeights: DblDyneVec read GetRawWeights write SetRawWeights; + property Weights: DblDyneVec read FWeights; + property Order: Integer read FOrder write SetOrder; end; @@ -54,56 +59,34 @@ uses { TMoveAvgFrm } -procedure TMoveAvgFrm.ResetBtnClick(Sender: TObject); +procedure TMoveAvgFrm.FormActivate(Sender: TObject); var - i: integer; + w: Integer; begin - OrderEdit.Text := ''; - ThetaEdit.Text := ''; - ThetaList.Clear; - CurrentIndex := 0; - for i := 0 to 20 do W[i] := 1.0; + w := MaxValue([HelpBtn.Width, ResetBtn.Width, CancelBtn.Width, OKBtn.Width]); + HelpBtn.Constraints.MinWidth := w; + ResetBtn.Constraints.MinWidth := w; + CancelBtn.Constraints.MinWidth := w; + OKBtn.Constraints.MinWidth := w; end; -procedure TMoveAvgFrm.ThetaEditEditingDone(Sender: TObject); -var - cellString: String; -begin - if CurrentIndex < 1 then - exit; - cellString := Format('Theta(%d) = %s', [currentIndex + 1, ThetaEdit.Text]); - ThetaList.Items[CurrentIndex] := cellString; - W[currentIndex + 1] := StrToFloat(ThetaEdit.Text); -end; - (* -procedure TMoveAvgFrm.ThetaEditKeyPress(Sender: TObject; var Key: char); -var - cellstring: string; -begin - if currentindex < 1 then exit; - if ord(Key) <> 13 then exit; - cellstring := 'Theta(' + IntToStr(currentindex + 1) + ') = ' + ThetaEdit.Text; - W[currentindex + 1] := StrToFloat(ThetaEdit.Text); -end; *) - -procedure TMoveAvgFrm.ThetaListClick(Sender: TObject); -var - index: integer; -begin - index := ThetaList.ItemIndex; - if index >= 0 then - begin - currentindex := index; - ThetaEdit.Text := '1.0'; - ThetaEdit.SetFocus; - end; -end; - -procedure TMoveAvgFrm.FormShow(Sender: TObject); +procedure TMoveAvgFrm.FormCreate(Sender: TObject); begin ResetBtnClick(self); end; +function TMoveAvgFrm.GetRawWeights: DblDyneVec; +var + r: Integer; +begin + SetLength(Result, WeightGrid.RowCount - 1); + for r := 1 to WeightGrid.RowCount - 1 do + if WeightGrid.cells[1, r] = '' then + Result[r-1] := 0.0 + else + Result[r-1] := StrToFloat(WeightGrid.Cells[1, r]); +end; + procedure TMoveAvgFrm.HelpBtnClick(Sender: TObject); begin if ContextHelpForm = nil then @@ -111,6 +94,38 @@ begin ContextHelpForm.HelpMessage((Sender as TButton).tag); end; +// Normalize all values so that their sum 1. +// (Except for center weight w[0] which must remain 1.0) +procedure TMoveAvgFrm.NormalizeWeights; +var + sum, x: double; + r: integer; +begin + if WeightGrid.RowCount = 1 then + exit; + + r := 1; + if not TryStrToFloat(WeightGrid.Cells[1, r], sum) then + sum := 0; + for r := 2 to WeightGrid.RowCount-1 do + if TryStrToFloat(WeightGrid.Cells[1, r], x) then + sum := sum + x * 2; + + if sum = 0 then + begin + FWeights[0] := 1.0; + for r := 1 to FOrder do Weights[r] := 0; + end else + for r := 1 to WeightGrid.RowCount-1 do + begin + if not TryStrToFloat(WeightGrid.Cells[1, r], x) then x := 0; + FWeights[r-1] := x / sum; + end; + + for r := 1 to WeightGrid.RowCount-1 do + WeightGrid.Cells[2, r] := FormatFloat('0.000', FWeights[r-1]); +end; + procedure TMoveAvgFrm.OKBtnClick(Sender: TObject); var msg: String; @@ -121,74 +136,57 @@ begin C.SetFocus; MessageDlg(msg, mtError, [mbOK], 0); ModalResult := mrNone; + exit; end; -end; - -procedure TMoveAvgFrm.ApplyBtnClick(Sender: TObject); -var - sum: double; - i: integer; - cellstring: string; -begin - ThetaList.Clear; - sum := W[0]; - for i := 1 to order do - sum := sum + 2.0 * W[i]; - for i := 0 to order do - begin - W[i] := W[i] / sum; - cellstring := 'Theta(' + IntToStr(i+1) + ') = ' + FloatToStr(W[i]); - ThetaList.Items.Add(cellstring); - end; -end; - -procedure TMoveAvgFrm.FormActivate(Sender: TObject); -var - wid: Integer; -begin - wid := MaxValue([HelpBtn.Width, ResetBtn.Width, ApplyBtn.Width, CancelBtn.Width, OKBtn.Width]); - HelpBtn.Constraints.MinWidth := wid; - ResetBtn.Constraints.MinWidth := wid; - ApplyBtn.Constraints.MinWidth := wid; - CancelBtn.Constraints.MinWidth := wid; - OKBtn.Constraints.MinWidth := wid; + NormalizeWeights; end; procedure TMoveAvgFrm.OrderEditEditingDone(Sender: TObject); var - i: Integer; + n: Integer; begin - ThetaList.Items.BeginUpdate; - try - ThetaList.Clear; - Order := StrToInt(orderEdit.Text); - for i := 1 to Order do - ThetaList.Items.Add('Theta(' + IntToStr(i) + ')'); - finally - ThetaList.Items.EndUpdate; - end; + if TryStrToInt(OrderEdit.Text, n) then + SetOrder(n); end; - (* -procedure TMoveAvgFrm.OrderEditKeyPress(Sender: TObject; var Key: char); -var - cellstring: string; - i: integer; +procedure TMoveAvgFrm.ResetBtnClick(Sender: TObject); begin - if ord(Key) <> 13 then exit; - ThetaList.Clear; - order := StrToInt(OrderEdit.Text); - for i := 1 to order do - begin - cellstring := 'Theta(' + IntToStr(i) + ')'; - ThetaList.Items.Add(cellstring); - end; + SetOrder(FOrder); +end; + +procedure TMoveAvgFrm.SetOrder(AValue: Integer); +var + i: Integer; +begin + FOrder := AValue; + SetLength(FWeights, FOrder + 1); + OrderEdit.Text := IntToStr(FOrder); + WeightGrid.RowCount := Length(FWeights) + WeightGrid.FixedRows; + WeightGrid.Cells[0, 1] := '0 (center)'; + for i := 2 to WeightGrid.RowCount-1 do + WeightGrid.Cells[0, i] := IntToStr(i-1); + NormalizeWeights; +end; + +procedure TMoveAvgFrm.SetRawWeights(const AValue: DblDyneVec); +var + r: Integer; +begin + FRawWeights := AValue; + if Length(FRawWeights) > FOrder+1 then + SetOrder(Length(FRawWeights) - 1); + for r := 1 to WeightGrid.RowCount-1 do + if r-1 < Length(FRawWeights) then + WeightGrid.Cells[1, r] := Format('%.3g', [FRawWeights[r-1]]) + else + WeightGrid.Cells[1, r] := ''; + NormalizeWeights; end; -*) function TMoveAvgFrm.Validate(out AMsg: String; out AControl: TWinControl): Boolean; var n: Integer; + x: Double; begin Result := false; @@ -205,16 +203,41 @@ begin exit; end; - if ThetaEdit.Text <> '' then + for n := 1 to WeightGrid.RowCount-1 do begin - AControl := ThetaEdit; - AMsg := 'Please press ENTER to add this input.'; - exit; + if WeightGrid.Cells[1, n] = '' then + begin + AMsg := 'Input required.'; + WeightGrid.Row := n; + WeightGrid.Col := 1; + AControl := WeightGrid; + exit; + end; + if not TryStrToFloat(WeightGrid.Cells[1, n], x) then + begin + AMsg := 'Number required.'; + WeightGrid.Row := n; + WeightGrid.Col := 1; + AControl := WeightGrid; + exit; + end; end; Result := true; end; +procedure TMoveAvgFrm.WeightGridEditingDone(Sender: TObject); +begin + NormalizeWeights; +end; + +procedure TMoveAvgFrm.WeightGridSelectEditor(Sender: TObject; aCol, + aRow: Integer; var Editor: TWinControl); +begin + if ACol = 2 then + Editor := nil; +end; + initialization {$I moveavgunit.lrs}