DifferenceAveragersImpl.Mesa
Spreitzer, June 18, 1986 11:55:12 am PDT
DIRECTORY DifferenceAveragers, BasicTime, Buttons, Containers, Imager, IO, Menus, Process, Real, RealFns, Rope, Sliders, VFonts, ViewerClasses, ViewerOps;
DifferenceAveragersImpl:
CEDAR
MONITOR
LOCKS a USING a: Averager
IMPORTS BasicTime, Buttons, Containers, Imager, IO, Process, Real, RealFns, Rope, Sliders, VFonts, ViewerOps
EXPORTS DifferenceAveragers
=
BEGIN OPEN DifferenceAveragers;
Time: TYPE = BasicTime.GMT;
Averager: TYPE = REF AveragerPrivate;
AveragerPrivate:
PUBLIC
TYPE =
MONITORED
RECORD [
logMin, logMax: INT,
min, scale: REAL,
samplePeriod, halfLife, originalHalfLife: Seconds,
w, weight: REAL,
weightedSums: ARRAY Role OF REAL,
lastTime: Time,
container, samplePeriodButton, halfLifeButton: Viewer ← NIL,
valueShowers: ARRAY Role OF Viewer ← ALL[NIL],
Sample: PROC [clientData: REF ANY] RETURNS [add, subtract: REAL],
clientData: REF ANY,
timeToSample: CONDITION,
sampler: PROCESS ← NIL
];
Role: TYPE = {add, subtract, difference};
lnOneHalf: REAL = RealFns.Ln[0.5];
halfMaxHalfLife: Seconds = LONG[12*60]*60;
sep1: INT ← 1;
sep2: INT ← 2;
timeWidth: INT ← VFonts.StringWidth["12:34:56"];
foreground: Imager.Color ← Imager.MakeGray[0.5];
Create:
PUBLIC
PROC
[
viewerInfo: ViewerClasses.ViewerRec ← [],
samplePeriod, halfLife: Seconds,
logMin, logMax: INT,
Sample: PROC [clientData: REF ANY] RETURNS [add, subtract: REAL],
clientData: REF ANY ← NIL
]
RETURNS [a: Averager]
= {
boundSP: Seconds = BoundSP[samplePeriod, halfLife];
a ←
NEW [AveragerPrivate ← [
logMin: logMin,
logMax: logMax,
min: RealFns.Power[10, logMin],
scale: 1.0/(logMax - logMin),
samplePeriod: boundSP,
halfLife: halfLife,
originalHalfLife: halfLife,
w: lnOneHalf/halfLife,
weight: 1.0/(1.0 - RealFns.Power[0.5, REAL[boundSP]/halfLife]),
weightedSums: ALL[0.0],
lastTime: BasicTime.Now[],
container: Containers.Create[viewerInfo],
Sample: Sample,
clientData: clientData
]];
TRUSTED {
Process.InitializeCondition[@a.timeToSample, Process.SecondsToTicks[a.samplePeriod]];
Process.EnableAborts[@a.timeToSample];
};
a.samplePeriodButton ← Buttons.Create[info: [parent: a.container, wx: 0, wy: 0, name: SPBName[a], border: FALSE], proc: TweakSamplePeriod, clientData: a, paint: FALSE];
a.halfLifeButton ← Buttons.Create[info: [parent: a.container, wx: a.samplePeriodButton.wx+a.samplePeriodButton.ww+sep1, wy: 0, border: FALSE, name: HLBName[a]], proc: TweakHalfLife, clientData: a, paint: FALSE];
FOR role: Role
IN Role
DO
dy: INT = a.container.ch/(Role.LAST.ORD+1);
a.valueShowers[role] ← Sliders.Create[info: [parent: a.container, wx: a.halfLifeButton.wx+a.halfLifeButton.ww+sep1, wy: dy*role.ORD, ww: 10, wh: dy, border: FALSE], orientation: horizontal, foreground: foreground, value: Normalize[a, 0.0], clientData: a, paint: FALSE];
Containers.ChildXBound[a.container, a.valueShowers[role]];
ENDLOOP;
ViewerOps.PaintViewer[a.container, all];
a.sampler ← FORK Sampler[a];
};
GetLayout:
PUBLIC
PROC [a: Averager, ancestor: Viewer]
RETURNS [samplePeriodButton, halfLifeButton, bars: XRange
--in client coords of ancestor (screen if NIL)--] = {
Loc:
PROC [child: Viewer]
RETURNS [loc: XRange] = {
loc ← [
left: ViewerOps.UserToScreenCoords[a.container, child.wx, child.wy].sx -
(IF ancestor # NIL THEN ViewerOps.UserToScreenCoords[ancestor, 0, 0].sx ELSE 0),
width: child.ww];
};
samplePeriodButton ← Loc[a.samplePeriodButton];
halfLifeButton ← Loc[a.halfLifeButton];
bars ← Loc[a.valueShowers[add]];
};
Normalize:
PROC [a: Averager, value:
REAL]
RETURNS [normalized:
REAL] =
INLINE {
normalized ← MIN[1.0, MAX[RealFns.Log[10, MAX[a.min, ABS[value]]]-a.logMin, 0] * a.scale]};
minFraction: REAL = 1.0/4096.0 --half of the 24 bits of mantissa in a REAL--;
maxRatio: REAL = -RealFns.Log[2.0, minFraction];
minRatio: REAL = -RealFns.Log[2.0, 1-minFraction];
BoundSP:
PROC [candidateSamplePeriod, halfLife: Seconds]
RETURNS [kosherSamplePeriod: Seconds] = {
kosherSamplePeriod ← Real.Round[MIN[maxRatio*halfLife, MAX[minRatio*halfLife, REAL[candidateSamplePeriod]]]]};
GoodSP:
PROC [halfLife: Seconds]
RETURNS [aGoodSamplePeriod: Seconds] = {
aGoodSamplePeriod ← 1};
TweakHalfLife:
PROC [parent:
REF
ANY, clientData:
REF
ANY ←
NIL,
mouseButton: Menus.MouseButton ← red, shift, control:
BOOL ←
FALSE]
--Buttons.ButtonProc-- = {
a: Averager = NARROW[clientData];
EnterAnd:
ENTRY
PROC [a: Averager] = {
ENABLE UNWIND => NULL;
newHalfLife: Seconds =
SELECT mouseButton
FROM
red => MAX[MIN[halfMaxHalfLife, a.halfLife]*2, 1],
yellow => a.originalHalfLife,
blue => MAX[a.halfLife/2, 1],
ENDCASE => ERROR;
IF newHalfLife # a.halfLife
THEN {
a.halfLife ← newHalfLife;
a.w ← lnOneHalf/a.halfLife;
Buttons.ReLabel[a.halfLifeButton, HLBName[a]];
InnerSetSamplePeriod[a, BoundSP[a.samplePeriod, a.halfLife]];
};
};
EnterAnd[a];
};
TweakSamplePeriod:
PROC [parent:
REF
ANY, clientData:
REF
ANY ←
NIL,
mouseButton: Menus.MouseButton ← red, shift, control:
BOOL ←
FALSE]
--Buttons.ButtonProc-- = {
a: Averager = NARROW[clientData];
SetSamplePeriod[
a,
SELECT mouseButton
FROM
red => BoundSP[a.samplePeriod*2, a.halfLife],
yellow => GoodSP[a.halfLife],
blue => BoundSP[MAX[a.samplePeriod/2, 1], a.halfLife],
ENDCASE => ERROR
];
};
SetSamplePeriod:
ENTRY
PROC [a: Averager, new: Seconds] = {
ENABLE UNWIND => NULL;
InnerSetSamplePeriod[a, new]};
InnerSetSamplePeriod:
INTERNAL
PROC [a: Averager, new: Seconds] = {
IF new # a.samplePeriod
THEN {
a.samplePeriod ← new;
Buttons.ReLabel[a.samplePeriodButton, SPBName[a]];
TRUSTED {Process.SetTimeout[@a.timeToSample, Process.SecondsToTicks[a.samplePeriod]]};
BROADCAST a.timeToSample;
};
};
AsViewer:
PUBLIC
PROC [a: Averager]
RETURNS [v: Viewer] = {
v ← a.container};
Sampler:
ENTRY
PROC [a: Averager] = {
ENABLE UNWIND => NULL;
DO
D: ARRAY Role OF REAL;
WAIT a.timeToSample;
[D[add], D[subtract]] ← a.Sample[a.clientData];
IF a.container.destroyed THEN EXIT;
{
now: Time = BasicTime.Now[];
period: Seconds = BasicTime.Period[a.lastTime, now];
keep: REAL = RealFns.Exp[a.w * period];
a.lastTime ← now;
D[difference] ← D[add] - D[subtract];
a.weight ← a.weight*keep + 1;
FOR role: Role
IN Role
DO
a.weightedSums[role] ← keep*a.weightedSums[role] + D[role];
Sliders.SetContents[a.valueShowers[role], Normalize[a, a.weightedSums[role]/a.weight]];
ENDLOOP;
}ENDLOOP;
};
SPBName:
PROC [a: Averager]
RETURNS [name:
ROPE] = {
name ← FmtPeriod[a.samplePeriod].Concat["/"]};
HLBName:
PROC [a: Averager]
RETURNS [name:
ROPE] = {
name ← FmtPeriod[a.halfLife]};
FmtPeriod:
PROC [period: Seconds]
RETURNS [asRope:
ROPE] = {
hours: INT = period/3600;
rem1: INT = period - hours*3600;
minutes: INT = rem1 / 60;
seconds: INT = rem1 - minutes*60;
asRope ← IO.PutFR["%02g:%02g:%02g", [integer[hours]], [integer[minutes]], [integer[seconds]]];
};
END.