📄 bfagent.m
字号:
// Code for a "bitstring forecaster" (BF) agent
/*
pj: change comments Nov. 2, 2001.
In 2000 comments, I said I was leaving the privateParams object as
a shared thing used by all BFagents. Well, I just could not stand
that anymore and so now I have the global BFParams object, the one
for which the GUI shows and it is the default for all instances,
but now each BFagent gets its own instance of BFParams and it is
a copy of BFParams, at least to start. That object privateParams
can now be individualized for each agent. In particular, one thing
on the TODO list has to be the individualization of bitlists.
Other changes described from last year are all still fine.
pj: change comments June 2, 2000
I began with the code as released by the ASM research team through
Brandon Weber in April, 2000. Here is a summary of the vital changes
that affect the BFagent class.
0. New classes used in this version:
A. BFCast.[hm]
B. BFParams.[hm]
C. BitVector.[hm]
1. I am fully aware of confusion about the meaning of bit in this
model. Bit does usually mean something that is 0 or 1, but in this
model it means something else. In the agent, a bit is what is often
called a "trit", something that is valued 00, 01, 10, or 11. Trit
means smallest thing that can hold three values. In these agents, a
trit has these meanings:
binary value integer equivalent meaning
00 0 # or "don't care"
01 1 NO
10 2 YES
11 3 not in use, a place holder value
GET READY for the big surprise. In World.m, the coding is reversed, so
01 means yes and 10 means no. This accelerates the comparision of the
agent's bit vector against the state of the world. Look for the & in
the-updateActiveList: medthod and you'll see why. I've written an
alternative, more transparent algorithm. I don't think it takes much
more time, but perhaps your computer will tell differently.
2. The bit math got frustrating enough that I have segregated it in a
class I call "BitVector". It is really a vector of "trits" but so
much of the rest of this code uses the term bit that I didn't have the
enthusiasm to change it. When a "BitVector" instance is created, it
has to be told how many bits, er trits, of information it is supposed
to hold. Whenever the agent needs to keep track of a bunch of bits,
er trits, it can create a BitVector object to do it, and the interface
allows values to be put in and retrieved in a relatively obvious way.
Furthermore, when agents create the "forecast objects" using the
BFCast class, then those forecast objects will contain within them
BitVector objects that keep track of that forcast's object's bits.
3. The BFCast class now has taken the place of the struct BF_cast that
was in BFagent.h. Any bit manipulation that needs to be done can be
done by talking to an instance of that class. The bit manipulation is
hidden from this BFagent class, so at some time in the future we could
re-implement BFCast and as long as it had the right interface, the
BFagent would not care. The BFCast class talks to the forecast object
that is inside it and tells it to set bits (er, trits) to certain
values with messages like "setConditionsbit: 5 To: 2" and BFCast can
handle the rest by passing on the news to the BitVector object.
4. The BFParams class now has taken the place of the struct BF_Params
that was in BFagent. This change allows some significant upgrades in
functionality. First, the BFParams class uses the Swarm
lispAppArchiver. See the initial values in asm.scm. Because the
BFParams object contains some variables that are derived from the
values in the archiver, it is necessary to send an "init" message
after creating a BFParams object. Second, it is now possible to
customize the agents by creating a customized BFParams object for each
agent. In the original ASM-2.0 code, there is a "global" variable
params and all agents use that one set of parameters. So far, I not
done much to investigate the advantages of allowing agents to use
different numbers of bits (er, trits), but I intend to. Until I do
that, the instance variable "privateParams" simply points to the same
single BFParams object that is created by ASMModelSwarm, and
parameters retrieved from either are thus the same.
<Apology mode> Note further, I did not write "get" methods for every
variable in the BFParams class. It just seemed onerous to do so.
There are 2 ways to get values out of BFParams. First, use the ->. I
declared the IVARS in BFParams to be public, so they can be retrieved
with the symbol -> as if the parameter object were a pointer to a
struct, as in: privateParams->condwords. I hate doing that, and have
the long term plan of replacing all of these usages with get messages.
Second, for the short term, I put in the getDouble() and getInt()
functions which can be used to do get values. These will work even for
private variables, so if you get concerned about declaring all those
public IVARS in BFParams, you can get values in this way:
getInt(privateParams,"condwords"). I used that a number of times in
this file, but not everywhere, because I got tired of typing.
</Apology mode>
5. More object orientation. The "homemade" linked lists, built with
pointers and other C concepts, are replaced by Swarm collections.
Iteration now uses Swarm index objects. Many usages of C alloc and
calloc are eliminated. This should approach a high level of readability.
Example: code that used to look like this for looping through a list:
struct BF_fcast *fptr, *topfptr;
topfptr = fcast + p->numfcasts;
for (fptr = fcast; fptr < topfptr; fptr++)
{
if (fptr->conditions[0] & real0) continue;
*nextptr = fptr;
nextptr = &fptr->next;
}
Now it looks like this:
id <Index> index=[ fcastList begin: [self getZone]];
for ( aForecast=[index next]; [index getLoc]==Member; aForecast=[index next] )
{
if ( [aForecast getConditionsWord: 0] & real0 ) continue ;
//if that's true, this does not get done:
[activeList addLast: aForecast];
}
[index drop];
Example 2: What was like this:
for (fptr = fcast; fptr < topfptr; fptr++)
{
agntcond = fptr->conditions;
for (i = 0; i < condbits; i++)
count[(int)((agntcond[WORD(i)]>>SHIFT[i])&3)][i]++;
}
Is now like this:
index=[ fcastList begin: [self getZone] ];
for( aForecast=[index next]; [index getLoc]==Member; aForecast=[index next] )
{
agntcond = [aForecast getConditions];
for (i = 0; i < condbits; i++)
{
count[ (int)[aForecast getConditionsbit: i]][i]++;
}
}
[index drop];
Note the usage of Swarm lists, indexes, and methods like
"getConditionsbit" and "getConditionsWord", instead of bitmath.
Usage of pointers to arrays is now minimized as well. There used to
be a class method called +prepareForTrading that would retrieve a copy
of all the world's information and pick out what was needed for an
agent of this class. Then the result of that calculation would get
stored in world. While this had the advantage of doing the
calculation once for all agents of the class, it has the disadvantage
of restricting us to having identical bit vectors in all agent
forecasts. I've dropped this approach, instead creating a variable
myworld in the agent's -prepareForTrading method, and each agent can
look to the world and get the information it wants.
6. Think locally, act locally. Global pointers and lists and anything
else have been replaced wherever possible by automatic variables
(inside methods) or instance variables. I created several new methods
that take bits from bit methods/functions and do them in isolation
(see -updateActiveList or -collectWorldData.
7. Genetic Algorithm now is written in Obj-C methods that pass whatever
arguments are needed, rather than using C functions that access a lot
of global variables. Agent's don't share workspace for the GA, either,
each has its own memory.
8. Formulas to calcuate strength, specfactor, and variance in the
forecast objects were different in the original BFagent.m than in
the bfagent.m. Since the bfagent.m file matched the documentation
released with ASM-2.0, I have changed to use the bfagent.m formulas
in this file. Some cleanup can still be made. */
#import "BFagent.h"
#import <random.h>
#import "World.h"
#include <misc.h>
#import "BFParams.h"
#import "BFCast.h"
#import "BitVector.h"
extern World *worldForAgent;
//pj: wish I could get rid of that one too, since each agent could
//just have a pointer pj: to a common world object. However, there are
//serveral class methods that use it.
//pj:
//convenience macros to replace stuff from ASM random with Swarm random stuff
#define drand() [uniformDblRand getDoubleWithMin: 0 withMax: 1]
#define urand() [uniformDblRand getDoubleWithMin: -1 withMax: 1]
#define irand(x) [uniformIntRand getIntegerWithMin: 0 withMax: x-1]
// Type of forecasting. WEIGHTED forecasting is untested in its
// present form.
//pj: bluntly, WEIGHTED does not work and is incomplete, It never worked
// in ASM-2.0, and that's why it is commented out by setting WEIGHTED to 0.
#define WEIGHTED 0
//pj: this is a static global declaration of the params object, shared by all instances.
//pj: note there is also a local copy which is, in current code, intitially the same thing,
//pj: and it never changes. The original code had 3 of these, so I'm slimmer by 1/3.
static BFParams * params;
//pj: other global variables were moved either to the performGA method where they are
//pj: needed or into the BFParams class, where they are used to create BFParams objects
//pj: ReadBitname moved to BFParams
//pj: This is the only global variable I still need, and I'm looking for a way go get rid of it!
static double minstrength;
// PRIVATE METHODS
@interface BFagent(Private)
//pj: methods now replace previous functions:
- (BFCast *) CopyRule:(BFCast *) to From: (BFCast *) from;
- (void) MakePool: (id <List>)rejects From: (id <Array>) list;
- (BOOL) Mutate: (BFCast *) new Status: (BOOL) changed;
- (BFCast *) Crossover:(BFCast *) newForecast Parent1: (BFCast *) parent1 Parent2: (BFCast *) parent2;
- (void) TransferFcastsFrom: newList To: forecastList Replace: rejects;
- (BFCast *) GetMort: (BFCast *) new Rejects: (id <List>) rejects;
- (void) Generalize: (id) list AvgStrength: (double) avgstrength;
- (BFCast *) Tournament: (id <Array>) list;
@end
@implementation BFagent
/*"The BFagent--"bitstring forecasting agent" is the centerpiece of
the ASM model. The agent competes in a stock market, it buy, it
sells. It decides to buy or sell by making predictions about what the
price of the stock is likely to do in future. In order to make
predictions, it keeps a large list of forecast objects on hand, and
each forecast object makes a price prediction. These forecasts, which
are created from the BFCast subclass, are fairly sophisticated
entities, they may monitor many different conditions of the world.
The forecast which has the best performance record at any given
instant is used to predict the future price, which in turn leads to
the buy/sell decision.
Inside the file BFagent.m, there is a long set of comments about the
updating that went on in the redesign of this code for ASM-2.2. In
order to faciliate this revision, several new classes were introduced.
BFParams is an object that keeps values of the parameters for
BFagents, and BFCast is the forecast object itself. BFCast, in turn,
keeps its conditions bits with a subclass called BitVector.
If you dig into the code of this agent, you will find a confusing
thing, so be warned. This code and articles based on it use the term
"bit" to refer to something that can be valued either 0, 1, or 2. 0
means "don't care," 1 means "NO" and 2 means "YES". The confusing
thing is that it takes two bits to represent this amount of
information. In binary, the values would be {00,01,10},
respectively. I'm told some people call these trits to keep that in
mind that two digits are required. As a result of the fact that it
takes "two bits" to store "one bit's" worth of information, some
relatively complicated book keeping has to be done. That's where all
the parameters like "condbits" and "condwors" come into play. In
ASM-2.0, that book keeping was all "manually done" right here in
BFagent.m, but in the 2.2 version, it is all hidden in the subclass
BitVector. So, for purposes of the interface of this class, a bit is
a 3 valued piece of information, and values of bits inside forecasts
are set by messages to the forecast, like [aForecast setConditionsbit:
bit FromZeroTo: 2], for example, will set that bit to 2. If you want
to know if a forecast has YES or NO for a bit x, [aForecast
getConditionsbit: x]. "*/
/*"This tells BFagents where they should look to get the default
parameters. it should give the agent an object from the BFParams
class."*/
+ (void)setBFParameterObject: x
{
params=x;
}
/*"This is vital to set values in the forecast class, BFCast, which in
turn initializes BitVector class"*/
+ (void)init
{
[BFCast init];
return;
}
/*"This creates the container objects activeList and oldActiveList.
In addition, it makes sure that any initialization in the createEnd
of the super class is done."*/
- createEnd
{
activeList=[List create: [self getZone]];
oldActiveList=[List create: [self getZone]];
return [super createEnd];
}
/*"initForecasts. Creates BFCast objects (forecasts) and puts them
into an array called fCastList. These are the "meat" of this
agent's functionality, as they are repeatedly updated, improved, and
tested in the remainder of the class. Please note each BFagent has
a copy of the default params object called privateParams. It can be
used to set individualized values of settings in BFParams for each
agent. That would allow true diversity! I don't see how that diversity
would be allowed for in the ASM-2.0."*/
- initForecasts
{
int sumspecificity = 0;
int i;
BFCast * aForecast;
int numfcasts;
id index;
// Initialize our instance variables
//all instances of BFagent can use the same BFParams object.
//ASM-2.0 was written that way, something like:
// privateParams= params;
// That seemed fraught with danger, with all instances having
// read/write access to a global parameter object, so now I'm
// creating a copy that each agent can have and individualize.
privateParams = [params copy: [self getZone]];
//If you want to customize privateParams, this is the spot!
numfcasts = getInt(privateParams,"numfcasts");
fcastList=[Array create: [self getZone] setCount: numfcasts];
avspecificity = 0.0;
gacount = 0;
variance = getDouble(privateParams, "initvar");
[self getPriceFromWorld];
[self getDividendFromWorld];
global_mean = price + dividend;
forecast = lforecast = global_mean;
// Initialize the forecasts, put them into Swarm Array
//keep the 0'th forecast in a "know nothing" condition
[fcastList atOffset: 0 put: [self createNewForecast]];
//create rest of forecasts with random conditions
for ( i = 1; i < numfcasts; i++)
{
id aForecast =[self createNewForecast] ;
[self setConditionsRandomly: aForecast];
[fcastList atOffset: i put: aForecast]; //put aForecast into Swarm array "fcastlist"
}
/* Compute average specificity */
//pj: Here is the proper way to iterate over Swarm collections
index=[ fcastList begin: [self getZone] ];
for( aForecast=[index next]; [index getLoc]==Member; aForecast=[index next] )
{
sumspecificity += [aForecast getSpecificity];
//[aForecast print];
}
avspecificity = (double) sumspecificity/(double)numfcasts;
[index drop];
return self;
}
/*"Creates a new forecast object (instance of BFCast), with all
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -