[From Bill Powers (2001.08.10.1937 MDT)]
Jeff Vancouver (2001.08.10.0905 EST)--
Okay, I got the code. Just to confirm, the model code can be found between
the "procedure autocontrol" and the "procedure controlmouse" lines. It that
correct?
Yes. Below is the whole procedure called "autocontrol" which substitutes
for the human participant. It's a little confusing because it contains
another procedure, "getrefs," which gives the model an x and y reference
position that moves smoothly around the rectangle. That's like some higher
system telling this control system where to want the little circle to be.
This procedure, like all the others in the simulation, is called just once
per iteration. The "angle" represents the direction of a ray from the
center of the square that swings slowly through 360 degrees (2*pi radians)
during one run of the demo. The x and y reference positions are obtained
from the place where this ray intersects the square (this could be done
much more simply). You'll notice that at the end of procedure "autocontrol"
the angle is incremented by a small amount in preparation for the next
iteration. At the end of "getrefs", a boolean variable "controlling" is set
false when one pass has been completed, to notify the rest of the program
that the run is over (it actually triggers an exit from the program). It
would have been more sensible to put that line at the end of the
autocontrol procedure, though the effect would have been the same.
The model control system starts with these lines:
getrefs(angle); {GET X AND Y REFERENCE POSITIONS FOR ANGLE}
autooutx := autooutx + (0.3*(autorefx - dotx) - autooutx)/250.0;
autoouty := autoouty + (0.3*(autorefy - doty) - autoouty)/250.0;
Autorefx (and -y) are "typed constants" set by the getrefs procedure,
micro-inching the reference position x,y around the square. A typed
constant keeps its value between calls of the procedure. The error signal
in the x direction is (autorefx - dotx), where dotx is the position where
the small circle is on the screen relative to the center of the square.
This is the model's perception of the dot position being compared with the
reference position. The human being, of course, just looks at the screen to
see the dot position.
Those two lines constitute a leaky integrator with a slowing factor of 250.
The next two lines are
mousex := mousex + round(autooutx + xrand);
mousey := mousey + round(autoouty + yrand);
This is how the model "moves the mouse." It simply alters the numbers
representing mouse position, where the person would alter them by moving
the physical mouse. Elsewhere you'll notice that when "auto" is true,
meaning the model is controlling, we do not read the actual mouse position
with the command "readmouse."
When there's no noise, xrand and yrand are zero, so we can ignore them. As
you can see, these lines make the x and y mouse positions into the
integrals of the variables autooutx and autoouty. We thus have a leaky
integrator followed by a pure integrator in the output function of the
model (the sort of thing that might happen in a two-level model). It makes
no difference whether the leaky integrator comes before or after the pure
integrator. This combination creates an unstable control system. The
combination of the gain (the factor of 0.3) and the slowing factor, here
set to 250, determines how unstable the system will be. I set these factors
so the model would behave more or less in the way I behave when doing this
task. Of course if we recorded the behavior of the human being we could do
a much better job of matching the model to what the person does. Anyone is
welcome to do this. It could make a nice master's thesis.
Note that adding the integrator makes the total output gain, for the steady
state, infinite: as long as there is any error at all, the mouse position
will keep changing.
The only difference in the program between the person controlling and the
mouse controlling is whether the mouse position is set by the person or by
the model (if "auto" is TRUE, the model is controlling):
if not auto then readmouse else
begin
autocontrol;
if not controlling then exit; {the run is finished}
end;
Remember that that little segment occurs once per iteration, about sixty
thousand times during a run."Autocontrol" computes a new reference position
very slightly different from the previous one, a new error, a new output,
and a very small change in mouse position. Then we go on to compute similar
small changes in the other parts of the program.
Best,
Bill P.
ยทยทยท
========================================================================
{EXCERPT FROM WHOLE PROGRAM}
procedure autocontrol;
const autooutx: double = 0.0;
autoouty: double = 0.0;
var autorefx,autorefy: double;
xrand, yrand: double;
procedure getrefs(a: double); {sub-procedure inside "autocontrol"}
var cornerrad: double;
begin
autorefx := 0.0; autorefy := 0.0;
cornerrad := halfside * sqrt(2.0);
if (a >= 0.0) and (a <= pi/4.0) then
begin
autorefx := halfside;
autorefy := cornerrad*sin(a);
end
else
if (a > pi/4.0) and (a <= 0.75*pi) then
begin
autorefy := halfside;
autorefx := cornerrad*sin(pi/2.0 - a);
end
else
if (a > 0.75*pi) and (a <= 1.25*pi) then
begin
autorefx := -halfside;
autorefy := cornerrad*sin(a);
end
else
if (a > 1.25*pi) and (a <= 1.75*pi) then
begin
autorefy := -halfside;
autorefx := cornerrad*sin(a - 1.5*pi);
end
else
if (a > 1.75*pi) and (a <= 2.0*pi) then
begin
autorefx := halfside;
autorefy := cornerrad*sin(a);
end;
controlling := a < 2.0*pi
end;
begin
getrefs(angle); {GET X AND Y REFERENCE POSITIONS FOR ANGLE}
if controlling then
begin
{X AND Y OUTPUT SIGNALS FROM SLOWED ERRORS}
autooutx := autooutx + (0.3*(autorefx - dotx) - autooutx)/250.0;
autoouty := autoouty + (0.3*(autorefy - doty) - autoouty)/250.0;
{MOUSE POSITION IS INTEGRAL OF OUTPUT SIGNAL}
if noisy then
begin
xrand := 1.0*(random - 0.5);
yrand := 1.0*(random - 0.5);
end
else
begin
xrand := 0.0;
yrand := 0.0;
end;
mousex := mousex + round(autooutx + xrand);
mousey := mousey + round(autoouty + yrand);
angle := angle + 1e-4; {change angle for next iteration}
end;
end;