unit Simhh;
{ ==================================================================
  WinWCP - Voltage-activated current simulation (c) J. Dempster 1996-99
  14/7/99 V3.0 .... Converted to 32 bit
  4/9/99 ... Display grid added
  30/10/99 ... NewFile method added
  24/1/99 .... Inactivation kinetics fixed
  25/2/02 .... Display now updated during simulation
               Progress is reported on main status bar
  24.6.03 ... No. of display grid lines can be changed
  04.12.05 .. New equation parameter input table
  ================================================================== }
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, TabNotBk, ExtCtrls, Global, Shared, FileIO, maths,
  ValEdit, ScopeDisplay, ComCtrls, math, ValidatedEdit, HTMLLabel, seslabio ;

type
  TVClampSim = class(TForm)
    QuantumGrp: TGroupBox;
    Label9: TLabel;
    GroupBox1: TGroupBox;
    bStart: TButton;
    bAbort: TButton;
    Label10: TLabel;
    GroupBox2: TGroupBox;
    Label8: TLabel;
    GroupBox7: TGroupBox;
    rbNoLeakSubtraction: TRadioButton;
    rbLeakSubtraction: TRadioButton;
    Label24: TLabel;
    scDisplay: TScopeDisplay;
    edVHold: TValidatedEdit;
    edVStep: TValidatedEdit;
    edNumRecords: TValidatedEdit;
    edLeakDivBy: TValidatedEdit;
    edNoiseRMS: TValidatedEdit;
    EquationGrp: TGroupBox;
    GroupBox4: TGroupBox;
    edGMax: TValidatedEdit;
    edGLeak: TValidatedEdit;
    edGSeries: TValidatedEdit;
    edVRev: TValidatedEdit;
    edPower: TValidatedEdit;
    edCm: TValidatedEdit;
    HTMLLabel1: THTMLLabel;
    HTMLLabel2: THTMLLabel;
    HTMLLabel3: THTMLLabel;
    HTMLLabel4: THTMLLabel;
    HTMLLabel5: THTMLLabel;
    HTMLLabel6: THTMLLabel;
    HTMLLabel7: THTMLLabel;
    MGroup: TGroupBox;
    edMInfVHalf: TValidatedEdit;
    edMinfVSlope: TValidatedEdit;
    edTauM: TValidatedEdit;
    HTMLLabel8: THTMLLabel;
    HTMLLabel9: THTMLLabel;
    HTMLLabel10: THTMLLabel;
    HTMLLabel11: THTMLLabel;
    GroupBox6: TGroupBox;
    edHinfVHalf: TValidatedEdit;
    edHinfVSlope: TValidatedEdit;
    edTauH: TValidatedEdit;
    HTMLLabel12: THTMLLabel;
    HTMLLabel13: THTMLLabel;
    HTMLLabel14: THTMLLabel;
    HTMLLabel15: THTMLLabel;
    ckInactivation: TCheckBox;
    procedure bStartClick(Sender: TObject);
    procedure bAbortClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormShow(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormResize(Sender: TObject);
    procedure scDisplayCursorChange(Sender: TObject);
    procedure edVRevKeyPress(Sender: TObject; var Key: Char);
    procedure edGMaxKeyPress(Sender: TObject; var Key: Char);
    procedure edGLeakKeyPress(Sender: TObject; var Key: Char);
    procedure edTauMKeyPress(Sender: TObject; var Key: Char);
    procedure edTauHKeyPress(Sender: TObject; var Key: Char);
  private
    { Private declarations }
    ADC : TSmallIntarray ;
    procedure UpdateDisplayScale ;
  public
    { Public declarations }
    procedure ChangeDisplayGrid ;
    procedure ZoomOut ;
    procedure NewFile ;
  end;

function Boltz( V, VHalf, VSlope : Single ) : Single ;
function AlphaFunction( V, VHalf, VSlope, Alpha0 : Single ) : Single ;
function HAlphaFunction( V, Alpha0 : Single ) : Single ;
var
  VClampSim: TVClampSim;

implementation

uses mdiform ;

{$R *.DFM}
const
     ChIm = 0 ;
     ChVm = 1 ;
    VMax = 0.2 ; { Note in volts}
type
    THHParameter = record
                 value : single ;
                 Inf : single ;
                 Zero : single ;
                 Tau : single ;
                 p : single ;
                 VSlope : Single ;
                 VHalf : Single ;
                 Alpha : Single ;
                 Alpha0 : Single ;
                 end ;


procedure TVClampSim.FormShow(Sender: TObject);
{ --------------------------------------
  Initialisations when form is displayed
  --------------------------------------}
begin

     { Initialise control buttons }
     bStart.Enabled := True ;
     bAbort.Enabled := False ;

     { Initialise display/controls }
     NewFile ;
     Resize ;
     UpdateDisplayScale ;

     end ;


procedure TVClampSim.NewFile ;
{ ---------------------------------------------------------
  Update controls/display to account for change of data file
  ---------------------------------------------------------}
var
   ch,i : Integer ;
begin

     if RawFH.NumRecords = 0 then RawFH.NumChannels := 2 ;
     
     { Set up scope display }
     scDisplay.MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     scDisplay.MinADCValue := Main.SESLabIO.ADCMinValue ;

     scDisplay.DisplayGrid := Settings.DisplayGrid ;
     scDisplay.NumHorizontalGridLines := Settings.NumHorizontalGridLines ;
     scDisplay.NumVerticalGridLines := Settings.NumVerticalGridLines ;
     
     scDisplay.MaxPoints := Settings.NumSamples ;
     scDisplay.NumPoints := Settings.NumSamples ;
     scDisplay.NumChannels := 2 ;

     { Set channel information }
     for ch := 0 to scDisplay.NumChannels-1 do begin
         Channel[ch].ChannelOffset := ch ;
         scDisplay.ChanOffsets[ch] := Channel[ch].ChannelOffset ;
         scDisplay.ChanUnits[ch] := Channel[Ch].ADCUnits ;
         scDisplay.ChanName[ch] := Channel[Ch].ADCName ;
         scDisplay.ChanScale[ch] := Channel[Ch].ADCScale ;
         Channel[Ch].yMin := scDisplay.MinADCValue ;
         Channel[Ch].yMax := scDisplay.MaxADCValue ;
         scDisplay.yMin[ch] := Channel[ch].yMin ;
         scDisplay.yMax[ch] := Channel[ch].yMax ;
         scDisplay.ChanVisible[ch] := True ;
         end ;

     scDisplay.xMin := 0 ;
     scDisplay.xMax := RawFH.NumSamples - 1 ;
     scDisplay.xOffset := 0 ;
     scDisplay.TScale := RawFH.dt*1000.0 ;
     scDisplay.TFormat := ' %.2f ms ' ;

     { Clear display }
     for i := 0 to scDisplay.NumChannels*Settings.NumSamples-1 do ADC[i] := 0 ;
     scDisplay.SetDataBuf( @ADC ) ;


     end;


procedure TVClampSim.bStartClick(Sender: TObject);
{ ---------------------------------------------------------
  Generate a file of simulated voltage-activated currents
  --------------------------------------------------------- }
var
   i,j,ch,iStart,iEnd : Integer ;
   Vm,Im,x,Gm : single ;
   NumRecordsDone,NumRecordsRequired,GroupNumber,NumPulses : Integer ;
   Rec : Integer ;
   FirstRecord : Boolean ;
   RH : TRecHeader ; { Record header }
   GMax,GLeak,GSeries,Cm,TauC,Ic,ITot : Single ;
   VRev,VHold,VStep,VOld,IMax,IScale,VScale,VTest,VLeak,Vp,NoiseRMS: Single ;
   m,h : THHParameter ;
   Done : Boolean ;
begin

     bStart.Enabled := False ;
     bAbort.Enabled := True ;

     NumRecordsRequired := Round(edNumRecords.Value) ;

     WriteToLogFile( 'Hodgkin-Huxley Voltage Clamp Simulation ' ) ;
     GMax := edGMax.Value ;
     WriteToLogFile( 'GMax = ' + edGMax.text ) ;
     GLeak := edGLeak.Value ;
     WriteToLogFile( 'GLeak = ' + edGLeak.text ) ;
     GSeries := edGSeries.Value ;
     WriteToLogFile( 'GSeries = ' + edGSeries.text ) ;
     Cm := edCm.Value ;
     WriteToLogFile( 'Cm = ' + edCm.text ) ;
     VRev := edVRev.Value ;
     WriteToLogFile( 'VRev = ' + edVRev.text ) ;
     VHold := edVHold.Value ;
     VStep := edVStep.Value ;
     { Activation gate (m) parameters }
     m.VHalf := edMInfVHalf.Value ;
     WriteToLogFile( 'm V.half = ' + edMInfVHalf.text ) ;
     m.VSlope := edMInfVSlope.Value ;
     WriteToLogFile( 'm V.slope = ' + edMInfVSlope.text ) ;
     m.Tau := edTauM.Value ;
     WriteToLogFile( 'm Tau = ' + edTauM.text ) ;
     m.Alpha := 0.5 / m.Tau ;
     m.Alpha0 := AlphaFunction( m.VHalf, m.VHalf, m.VSlope, 1. ) ;
     m.Alpha0 :=  m.Alpha / m.Alpha0 ;
     m.P := edPower.Value ;
     WriteToLogFile( 'm Power factor = ' + edPower.text ) ;
     { Sampling interval }

     if ckInactivation.checked then begin
        { Inactivation gate (h) parameters }
        h.VHalf := edHInfVHalf.Value ;
        WriteToLogFile( 'h V.Half = ' + edHInfVHalf.text ) ;
        h.VSlope := edHInfVSlope.Value ;
        WriteToLogFile( 'h V.Slope = ' + edHInfVSlope.text ) ;
        h.Tau := edTauH.Value ;
        WriteToLogFile( 'h Tau = ' + edTauH.text ) ;
        h.Alpha := 0.5 / h.Tau ;
        h.Alpha0 := HAlphaFunction( h.VHalf, 1. ) ;
        h.Alpha0 :=  h.Alpha / h.Alpha0 ;

        h.P := 1. ;
        end ;

     { Capacity current time constant }
     TauC := Cm / GSeries ;
     { Background noise RMS }
     NoiseRMS := edNoiseRMS.Value ;

     { Set channel parameters }

     { If this is the first record in the file create
     an appropriate scaling factor }
     if RawfH.NumRecords = 0 then begin

        RawfH.NumChannels := scDisplay.NumChannels ;
        RawFH.NumSamples := scDisplay.NumPoints ;

        NewFile ;

        UpdateDisplayScale ;

        Channel[ChIm].ADCScale := scDisplay.ChanScale[ChIm];
        RH.ADCVoltageRange[ChIm] := 1. ;
        Channel[ChIm].ADCCalibrationFactor := RH.ADCVoltageRange[ChIm] /
                                  ( Channel[ChIm].ADCScale * (Main.SESLabIO.ADCMaxValue+1) ) ;

        { Set voltage channel scaling factor }
        Channel[ChVm].ADCScale := scDisplay.ChanScale[ChVm];
        scDisplay.ChanScale[ChVm] := Channel[ChVm].ADCScale ;
        RH.ADCVoltageRange[ChVm] := 1. ;
        Channel[ChVm].ADCCalibrationFactor := RH.ADCVoltageRange[ChVm] /
                                  ( Channel[ChVm].ADCScale * (Main.SESLabIO.ADCMaxValue+1) ) ;

        RawFH.dt := scDisplay.TScale / 1000.0 ;
        end
     else begin
        GetRecordHeaderOnly( RawfH, RH, RawfH.NumRecords ) ;
        GroupNumber := Round(RH.Number) ;
        end ;

     IMax := Channel[ChIm].ADCScale*Main.SESLabIO.ADCMaxValue*nAToAmps ;

     { Set channel magnification }
     for ch := 0 to RawfH.NumChannels-1 do begin
         Channel[Ch].ADCZero := 0 ;
         Channel[Ch].xMin := 0 ;
         Channel[Ch].xMax := RawfH.NumSamples-1 ;
         Channel[Ch].yMin := Main.SESLabIO.ADCMinValue ;
         Channel[Ch].yMax := Main.SESLabIO.ADCMaxValue ;
         Channel[Ch].InUse := True ;
         end ;

     Channel[CHVm].ADCName := 'Vm' ;
     Channel[CHVm].ADCUnits := 'mV' ;
     Channel[CHIm].ADCName := 'Im' ;
     Channel[CHIm].ADCUnits := 'nA' ;

     // Set A/D sample range
     MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     MinADCValue := Main.SESLabIO.ADCMinValue ;

     GroupNumber := 0 ;

     { Set voltage channel to fixed zero level }
     Channel[ChVm].ADCZeroAt := -1 ;
     CHannel[ChVm].ADCZero := 0 ;
     { Current channel zero level calculated frm samples 0..19}
     Channel[ChIm].ADCZeroAt := 0 ;
     SaveHeader( RawFH ) ;
     FirstRecord := False ;

     { Volts --> bits scale factor }
     VScale := Channel[ChVm].ADCScale / VoltsTomV ;
     { Amps --> bits scale factor }
     IScale := Channel[ChIm].ADCScale / AmpsTonA ;

     NumRecordsDone := 0 ;
     Done := False ;
     while not Done do begin

        VTest := (NumRecordsDone+1)*VStep ;
        iStart := RawFH.NumSamples div 10 ;
        iEnd := RawFH.NumSamples - iStart ;
        Inc(GroupNumber) ;

        { ** Create leak subtraction pulse ** }

        if rbLeakSubtraction.checked then begin
           { ** P/N leak subtraction ** }
           NumPulses := Round(edLeakDivBy.Value)  ;
           If (NumPulses = 0) or (Abs(NumPulses)>200) then NumPulses := -4 ;
           VLeak := VTest / NumPulses ;

           {Create leak pulse record }
           VOld := VHold ;
           x := 0. ;
           Ic := 0. ;
           for i := 0 to RawFH.NumSamples-1 do begin
               if (i >= iStart) and (i <= iEnd) then Vp := VHold + VLeak
                                                else Vp := VHold ;

               {Voltage channel }
               j := i*RawFH.NumChannels + ChVm ;
               ADC[j] := Round( Vp/VScale ) ;

               { Find membrane potential after voltage drop along
               pipette series resistance }
               Vm := Vp / ( 1. + GLeak/GSeries ) ;

               { Current channel }
               Im := (Vm * GLeak) + RandG(0.0,NoiseRMS) /
                            sqrt(MaxFlt([Abs(NumPulses),1. ])) ;

               { Add capacity current }
               if Vp <> VOld then begin
                  Ic := (Vp - Vold) * GSeries ;
                  x := 0. ;
                  end ;
               VOld := Vp ;
               Im := Im + Ic*exp( -x/TauC ) ;

               { Keep within display limits }
               Im := Max(Min(Im,IMax),-IMax) ;
               x := x + RawFH.dt ;

               j := i*RawFH.NumChannels + ChIm ;
               ADC[j] := Round( Im/IScale ) ;
               end ;

           { Save leak record to file }
           Inc(RawFH.NumRecords) ;
           RH.Status := 'ACCEPTED' ;
           RH.RecType := 'LEAK' ;
           RH.Number := GroupNumber ;
           RH.Time := RH.Number ;
           RH.dt := RawfH.dt ;
           RH.Equation.Available := False ;
           RH.Analysis.Available := False ;
           RH.Ident := ' ' ;
           PutRecord( RawfH, RH, RawfH.NumRecords, ADC ) ;
           end ;

        { ** Create test pulse ** }

        x := 0. ;
        Gm := GLeak ;
        Vm := VHold / ( 1. + Gm/GSeries ) ;
        { Initial values of m and h parameters }
        m.value := Boltz( Vm, m.VHalf, m.VSlope ) ;
        if ckInactivation.checked then h.value := Boltz( Vm, h.VHalf, h.VSlope ) ;
        Ic := 0. ;
        VOld := VHold ;
        for i := 0 to RawFH.NumSamples-1 do begin
            if (i > iStart) and (i < iEnd) then Vp := VHold + VTest
                                           else Vp := VHold ;
            {Voltage channel }
            j := i*RawFH.NumChannels + ChVm ;

            { Find membrane potential after voltage drop along
            pipette series resistance }

            Vm := Vp / ( 1. + Gm/GSeries ) ;
            ADC[j] := Round( Vp/VScale ) ;
            { Current channel }
            { ACTIVATION KINETICS }
            {Calculate m.zero & m.infinity from Boltzmann function }
            m.Inf := Boltz( Vm, m.VHalf, m.VSlope ) ;
            { Calculate activation rate constant }
            m.Alpha := AlphaFunction( Vm, m.VHalf, m.VSlope, m.Alpha0 ) ;
            { Calculate time constant }
            m.Tau := m.Inf / m.Alpha ;
            { Calculate value of activation parameter }
            m.value := m.Inf - (m.Inf - m.value)*exp( -RawFH.dt/m.Tau ) ;
            { Calculate conductance }
            Gm := GMax*Power(m.value,m.p) ;

            { INACTIVATION KINETICS }
            if ckInactivation.checked then begin
               h.Inf := Boltz( Vm, h.VHalf, h.Vslope ) ;
               h.Zero := Boltz( VHold, h.VHalf, -h.Vslope ) ;
               h.Alpha := HAlphaFunction( Vm, h.Alpha0 ) ;
               h.Tau := h.Inf / h.Alpha ;
               h.value := h.Inf - (h.Inf - h.Value)*exp( -RawFH.dt/h.Tau ) ;
               Gm := Gm*h.value ;
               end ;
            { Add leak conductance }
            Gm := Gm + GLeak ;
            { Calculate current }
            Im := (Vm - VRev)*Gm + RandG(0.0,NoiseRMS) ;

            { Add capacity current }
            if Vp <> VOld then begin
               Ic := (Vp - Vold) * GSeries ;
               x := 0. ;
               end ;
            VOld := Vp ;
            x := x + RawFH.dt ;

            Im := Im + Ic*exp( -x/TauC ) ;
            {Keep within display limits }
            Im := MaxFlt([MinFlt([Im,IMax]),-IMax]) ;

            j := i*RawFH.NumChannels + ChIm ;
            ADC[j] :=  Round( Im/IScale ) ;
            end ;

        { Force a display of new sweep }
        scDisplay.TScale := RawFH.dt*Settings.TScale ;
        scDisplay.SetDataBuf( @ADC ) ;

        { Save Record to file }
        Inc(RawFH.NumRecords) ;
        RH.Status := 'ACCEPTED' ;
        RH.RecType := 'TEST' ;
        RH.Number := GroupNumber ;
        RH.Time := RH.Number ;
        RH.dt := RawfH.dt ;
        RH.Equation.Available := False ;
        RH.Ident := ' ' ;

        PutRecord( RawfH, RH, RawfH.NumRecords, ADC ) ;

        Inc(NumRecordsDone) ;
        { Terminate when all records done }
        Main.StatusBar.SimpleText := format(
        'Voltage-activated Currents Simulation : Record %d/%d created.',
        [NumRecordsDone,NumRecordsRequired] ) ;

        if (NumRecordsDone >= NumRecordsRequired) or bStart.Enabled then Done := True ;

        { Allow other activities during loop }
        scDisplay.Invalidate ;
        Application.ProcessMessages ;

        end ;

     { Save file header data }
     SaveHeader(RawFH) ;

     Main.StatusBar.SimpleText := format(
     ' Voltage-activated Currents Simulation : %d records created.',
     [NumRecordsDone] ) ;


     bStart.Enabled := True ;
     bAbort.Enabled := False ;

     end ;

procedure TVClampSim.UpdateDisplayScale ;
// ----------------------
// Update display scaling
// ----------------------
var
    IMax : Single ;
    GMax : Single ;
    GLeak : Single ;
    VRev : Single ;
begin

     GMax := edGMax.Value ;
     VRev := edVRev.Value ;
     GLeak := edGLeak.Value ;
     IMax := Max( Abs(GMax*(VMax-VRev) + GLeak*VMax),
                  Abs(GMax*(-VMax-VRev) - GLeak*VMax) ) ;
     if IMax = 0. then IMax := 1E-9 ;

     scDisplay.ChanScale[ChIm] := AmpsTonA*IMax / (Main.SESLabIO.ADCMaxValue+1) ;
     scDisplay.ChanScale[ChVm] := (VMax*VoltsTomV) / (Main.SESLabIO.ADCMaxValue+1) ;

     scDisplay.TScale := ((edTauM.Value + edTauH.Value)*4000.0)/RawFH.NumSamples ;
     scDisplay.TFormat := ' %.2f ms' ;

     scDisplay.Invalidate ;

     end ;


procedure TVClampSim.bAbortClick(Sender: TObject);
{ --------------------
  Abort simulation run
  -------------------- }
begin
     bStart.Enabled := True ;
     bAbort.Enabled := False ;
     end;


function Boltz( V, VHalf, VSlope : Single ) : Single ;
begin
     Boltz := 1. / ( 1. + Exp( -(V - VHalf)/VSlope ) ) ;
     end ;


function AlphaFunction( V, VHalf, VSlope, Alpha0 : Single ) : Single ;
var
   A0,A1,V0,V1 : Single ;
begin
     if Abs(VHalf-V) > 1E-5 then
        AlphaFunction := (Alpha0*(VHalf-V)) / (exp((VHalf-V)/VSlope) - 1. )
     else begin
        V0 := -1E-5 ;
        A0 := (Alpha0*(VHalf-V0)) / (exp((VHalf-V0)/VSlope) - 1. ) ;
        V1 := 1E-5 ;
        A1 := (Alpha0*(VHalf-V1)) / (exp((VHalf-V1)/VSlope) - 1. ) ;
        AlphaFunction := (A0+A1)*0.5 ;
        end ;
     end ;

function HAlphaFunction( V, Alpha0 : Single ) : Single ;
begin
     HAlphaFunction := Alpha0*exp( -(V-0.06) / 0.02 ) ;
     end ;


procedure TVClampSim.FormClose(Sender: TObject; var Action: TCloseAction);
{ ----------------------
  Close and destroy form
  ---------------------- }
begin

     if RawFH.NumRecords > 0 then begin
        Main.mnShowRaw.Enabled := True ;
        Main.mnShowRaw.Click ;
        end ;

     Action := caFree ;
     end;


procedure TVClampSim.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
begin
     if not bStart.Enabled then CanClose := False
                           else CanClose := True ;
     end;

procedure TVClampSim.FormResize(Sender: TObject);
{ ------------------------------------------------------
  Adjust size/position of controls when form is re-sized
  ------------------------------------------------------ }
begin
      EquationGrp.Top := ClientHeight - EquationGrp.Height - 5 ;
      EquationGrp.Width := Max( ClientWidth - EquationGrp.Left - 5, 2 ) ;
      scDisplay.Height := Max( EquationGrp.Top - scDisplay.Top - 5, 2 ) ;
      scDisplay.Width := Max( ClientWidth - scDisplay.Left - 5, 2 ) ;
      end;


procedure TVClampSim.ChangeDisplayGrid ;
{ --------------------------------------------
  Update grid pattern on oscilloscope display
  -------------------------------------------- }
begin
     scDisplay.MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     scDisplay.MinADCValue := Main.SESLabIO.ADCMinValue ;
     scDisplay.DisplayGrid := Settings.DisplayGrid ;
     scDisplay.NumHorizontalGridLines := Settings.NumHorizontalGridLines ;
     scDisplay.NumVerticalGridLines := Settings.NumVerticalGridLines ;

     scDisplay.Invalidate ;
     end ;


procedure  TVClampSim.ZoomOut ;
{ ---------------------------------
  Set minimum display magnification
  --------------------------------- }
begin
     scDisplay.MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     scDisplay.MinADCValue := Main.SESLabIO.ADCMinValue ;
     scDisplay.ZoomOut ;
     end ;

procedure TVClampSim.scDisplayCursorChange(Sender: TObject);
var
   ch : Integer ;
begin
     { Update channel descriptors with any changes to display }
     for ch := 0 to scDisplay.NumChannels-1 do if Channel[ch].InUse then begin
         Channel[Ch].yMin := scDisplay.YMin[Ch] ;
         Channel[Ch].yMax := scDisplay.YMax[Ch] ;
         end ;
     end;


procedure TVClampSim.edVRevKeyPress(Sender: TObject; var Key: Char);
begin
     if Key = #13 then UpdateDisplayScale ;
     end;

procedure TVClampSim.edGMaxKeyPress(Sender: TObject; var Key: Char);
begin
     if Key = #13 then UpdateDisplayScale ;
     end;

procedure TVClampSim.edGLeakKeyPress(Sender: TObject; var Key: Char);
begin
     if Key = #13 then UpdateDisplayScale ;
     end;

procedure TVClampSim.edTauMKeyPress(Sender: TObject; var Key: Char);
begin
     if Key = #13 then UpdateDisplayScale ;
     end;

procedure TVClampSim.edTauHKeyPress(Sender: TObject; var Key: Char);
begin
     if Key = #13 then UpdateDisplayScale ;
     end;

end.
