|
25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more... |
Home Projects Up Sitemap Search Blog Forum+Chat About Us Privacy Terms of Use Feedback FAQ Images Services Ads Donate Humor |
|
|
John Holland-style classifier system demonstrates natural selection and evolution. C++ console program for MSDOS.Classify.cpp creates and runs a classifier system (a type of "complex system"). It was inspired by the description of John Holland's classifier system in the book Complexity, by M. Mitchell Waldrop. The book was my only source of information on how to create a classifier system, and it is NOT by any means a step-by-step manual! Its description and discussion of the method did, however, provide sufficient information to create this working model. Having been built with only one source of information and a lot of guessing, this implementation is probably unusual compared to others. Amazingly, though, it does work. Arbitrary text strings define the "problem", the "solution", the "stimulus" that triggers each rule to respond, and the "response" that each rule gives. Weak rules that do not contribute toward a solution do not propagate and are killed off. Strong contributing rules are crossbred to create child rules, with mutations. The system eventually produces a ruleset that is able to solve the problem. Thus, the program demonstrates how variation and natural selection in the presence of environmental pressures produce evolution in a population. It was developed with Borland C++ 4.0 for MSDOS, and uses Borland BGI graphics. The most generally useful part of this program is probably the C++ "mating constructors" that use a genetic algorithm to create a new object that has a mix of characteristics from each of two parent objects. The methods are easily modified to other purposes, and I've used them in several other programs. One of these constructors is farther down this page, and two used for the Rule class begin on the next page. The project is somewhat overly modular to allow for expansions that were never done. Each component in its own source file. This screenshot might help set up the project nodes:
You can display the status of the system in either text or graphics mode: In graphic mode, each Rule is shown as a column in a bar chart. The height shows the strength (bid value), and the colors encode whether it was or wasn't a recent lottery bidder or winner. The screenshot is enlarged. Each column is actually 1 pixel wide.
In text mode, data about 20 of the classifiers are shown: # Stimulus Response Bid MsgBoard Problem Solution ----- ------------------- ---------- --------- -------- -------- 0: 1#1###1# 00111001 - 90 01100010 01010101 10011101 1: 11##111# 01111001 90 00110101 01010101 10011101 2: ###00### 10010011 90 10011000 01010101 10011101 3: #0#1#001 01010111 90 11001000 01010101 10011101 4: 01#####0 10101011 90 01000000 01010101 10011101 5: ##1#01#0 01111101 - 90 s01010101 01010101 10011101 6: 100#010# 10010000 90 01010101 10011101 7: 11#00000 11011111 90 01010101 10011101 8: ####0#1# 01100010 W 90 01010101 10011101 9: #1#####1 10100011 - 90 01010101 10011101 10: 10##0##0 00001101 90 01010101 10011101 11: ###0#### 00000000 - 90 01010101 10011101 12: 0#101#10 01011101 90 01010101 10011101 13: #0#1#1## 01001010 - 90 01010101 10011101 14: 1000##01 11111101 90 01010101 10011101 15: 01#01### 01011111 - 90 01010101 10011101 16: ####11## 00011011 90 01010101 10011101 17: 0100##11 00110100 90 01010101 10011101 18: 0#1#11#0 01000000 90 01010101 10011101 19: 00#0111# 01101111 90 01010101 10011101 Child: 123. Total: 1800. Iterations: 562. Rules: 123 ------------------------------------------------------------------------ Paused. Press any key... This program's purpose and methods aren't documented well in any single location. Reading Waldrop's Complexity, and then this program's project notes in Complex.doc, and then the program's source code comments would be the best route to understanding it. There is a lot of jargon related to classifier systems. As you read the above sources, you'll become familiar with the vocabulary. Download:Click here to download classifycpp.zip (about 66 KB) In addition to the code listed on these pages, the zip file contains:
|
/* main.cpp 1-25-99
This file is part of the CLASSIFY classifier system project.
Copyright (C)1995-99 Steven Whitney.
Initially published by http://25yearsofprogramming.com.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License (GPL)
Version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
A John Holland-style classifier system, as described in "Complexity", by M. Mitchell Waldrop.
The book was my ONLY source of information on how to create a classifier system. It is
NOT a step-by-step manual!, but its description and discussion of the method did provide
enough information for me to create this working model, which is probably quite
peculiar compared to other programs people have written to do this.
Its graphics and reports are fun and interesting to watch, but the program doesn't do anything
useful. It evolved into a sort of crude classifier system laboratory whose purpose was to compare
various possible methods and parameters in the classifier system. I never really studied it
much, though, because the classifier model proved unwieldy. You have to encode the problem,
encode the rules specific to it, encode the interpretation of the rules, and define what
outcome constitutes a "successful" solution. And all this has to be done for every
problem you want a classifier system to solve. I found this to be too much overhead
machinery to be a practical method for solving real problems. So I kept the lottery system
and the bucket brigade passback, but put them into a rule-based system, avoiding the
bit-encoded or byte-encoded classifier system itself. Using English-encoded
rules avoids the complications of encoding and decoding arbitrary strings of bits or bytes
that mean nothing to a human obsever.
NOTE that this is now the primary source file (for To Do list, etc.), not classify.cpp.
------
TO DO:
review entire program, all files, carefully.
look for // #error lines
change lottery, etc. execution order to match book.
[9/27/2006: The methods developed in this program were turned into a more rule-based type
system, generalized, and put to use in the WTalk.ide project. Its "classifier-like system"
is used to reward or punish "reply rules" according to how the human user rated the response
that was generated.]
maybe pull out bb also into its own class BulletinBoard.
One reasonable organization for Classifier is that it HAS as its members
an environment external, the outside (simulation)
a ruleset internal, how to deal with the outside
a bulletinboard interface, where the 2 "meet".
in statustext: numbering 0-19 pointless: issue each rule an ID#, and show those,
so you can track them. don't repeat problem and solution all the way down the
page. show once, and use the space to display the Classifier constants somewhere,
labeled!
Write a classify.txt file similar to adapt.txt, that describes what you'll
be watching, especially for options 1 and 2 (is it 4 consecutive problems now?, etc.)
Note also that the text status display only shows the first 20 rules, and
thus the winner may not be shown.
write the byte<->string conversions mentioned in complex.doc.
do a long evolve run from scratch to test wholeset.tim.
Before implementing any features involving variable size strings, get the
current version in a basically stable and final form. This and some other
proposed changes may be wandering so far from the original classifier idea
that they should be implemented in completely new programs, or kept merely as
ideas for future classifier programs that have a specific use.
all other notes are in COMPLEX.DOC.
------
Files:
finalset.dat = ending constants from latest evolve run
finalset.sav = archive of sets that were particularly good (maintained manually)
finalset.tim = times from latest time trial run
wholeset.tim = times for the whole set at intervals during evolve()
*/
#include "classify.h"
extern unsigned _stklen = 64000U;
////////////////////////////////////////////////////////////////////////////
// global data
////////////////////////////////////////////////////////////////////////////
constream scr; // console screen
////////////////////////////////////////////////////////////////////////////
//
//--------------------------------------------------------------------------
// watch a single classifier learn
void watchsingle(BOOL userandom)
{
scr << Classifier::helptext(); // should always be in text mode on entry
presskey(scr);
BOOL readfromdisk = FALSE;
ifstream infile;
if(yesno("Load Classifiers sequentially from disk file FINALSET.DAT",scr))
{
readfromdisk = TRUE;
infile.open("finalset.dat");
}
BOOL doanother = TRUE;
while(doanother)
{
restorecrtmode();
Environment e;
Classifier c(&e,userandom); // new one each time, with a new set of starting rules
if(readfromdisk) // if reading from file, override with file data
{
infile >> ws; // force fail if at end of file
if(infile.fail())
{
scr << "No more file data. Subsequent classifiers will be ";
scr << (userandom ? "random." : "standard.") << endl;
infile.close();
readfromdisk = FALSE;
}
else
infile >> c;
}
scr << c << endl; // show constants
presskey(scr);
if(Classifier::displaymode == Classifier::graphics)
setgraphmode(VGAHI); // restore graphics mode
for(int i = 0 ; i < 7 ; i++) // number of problems each Classifier solves
{
switch(i)
{
// repeating problems tests both learning and retention.
case 0: e.setproblem(0); break; // first random problem
case 1: e.setproblem(1); break; // second random problem
case 2: e.setproblem(0); break; // first problem again
case 3: e.setproblem(1); break;
case 4: e.setproblem(0); break;
case 5: e.setproblem(2); break;
case 6: e.setproblem(3); break;
}
if(c.run() == 27)
break;
}
// c.rulestodisk(); // write rule set to disk
restorecrtmode();
doanother = yesno("Do another",scr);
}
} // end watchsingle
//--------------------------------------------------------------------------
// create a set of classifiers and allow them to evolve
// A user ESC during any Classifier's run() function will cause save & return.
// A user Q will abort that Classifier, go to the next.
// All Classifiers work on the same problem set.
void evolveclassifiers()
{
Classifier::beepon = Classifier::statusreport = FALSE; // no beep, no displays
Classifier::displaymode = Classifier::text;
const int csetsize = 8; // number of Classifiers to create & maintain
Environment e;
TIArrayAsVector<Classifier> cset(csetsize,0,1); // the evolving set
// this allows continuing a previous run or specifying all starting constants
if(yesno("Load starting Classifiers from disk file FINALSET.DAT",scr))
{
ifstream infile("finalset.dat");
Classifier c(&e);
while(infile >> c) // read all that are there
cset.Add(new Classifier(c)); // uses copy constructor
}
// fill to csetsize with randoms, whether some were from file or not
while(cset.GetItemsInContainer() < csetsize) // create initial random set
cset.Add(new Classifier(&e,TRUE));
ofstream timerfile("wholeset.tim");
Stopwatch runtimer; // marks off timing intervals
Stopwatch passtimer; // times each pass
long fastest1, fastest2; // first and second fastest times so far
runtimer.start();
for(long passcount = 0L; ; passcount++) // each pass starts here
{
fastest1 = fastest2 = 600L; // even parents have to beat 10 min.
e.flushproblems(); // flush & generate first problem
e.setproblem(3); // set highest problem we'll need
passtimer.start();
int ccount = cset.GetItemsInContainer(); // for each Classifier,
for(int i = 0 ; i < ccount ; i++)
{
cset[i]->rs.newruleset(); // empty trained rule sets from previous pass
cset[i]->sw.reset(); // time only problems solved this pass
scr << "Memory available = " << farcoreleft() << " " << TTime() << endl;
scr << endl << "Classifier " << i << " constants = " << *(cset[i]) << endl;
// if too many times are the same, you need more problems
for(int j = 0 ; j < 7 ; j++) // number of problems to solve
{
switch(j)
{
// repeating problems tests both learning and retention.
case 0: e.setproblem(0); break; // first random problem
case 1: e.setproblem(1); break; // second random problem
case 2: e.setproblem(0); break; // first problem again
case 3: e.setproblem(1); break;
case 4: e.setproblem(0); break;
case 5: e.setproblem(2); break;
case 6: e.setproblem(3); break;
}
scr << "Classifier " << i << " Problem " << j << " ";
// allow only (time to beat) - (time you've already used up)
int ch = cset[i]->run(fastest2 - cset[i]->sw.totaltime);
if(ch == 27) // user aborted run, finished
{
if(yesno("Save final set to disk",scr))
{
ofstream outfile("finalset.dat"); // save final set to disk
for(int k = 0 ; k < ccount ; k++)
outfile << *(cset[k]) << endl;
}
cset.Flush(TShouldDelete::Delete);
return;
}
if(ch == 'q') // user aborted this one, or time limit exceeded
{
scr << "Too slow or user aborted." << endl;
break;
}
} // here, the Classifier has solved all 4 problems
scr << " Total time = ";
scr << cset[i]->sw.totaltime << " seconds." << endl;
if(cset[i]->sw.totaltime <= fastest1)
{
fastest2 = fastest1;
fastest1 = cset[i]->sw.totaltime;
}
else
fastest2 = min(fastest2,cset[i]->sw.totaltime);
} // end for(each Classifier)
while(cset.GetItemsInContainer() > 2) // kill weakest until only 2 left
{
Classifier* weakest = cset[0]; // start first as the weakest
int ccount = cset.GetItemsInContainer();
for(i = 0 ; i < ccount ; i++) // if slower,
if(cset[i]->sw.totaltime > weakest->sw.totaltime) // tie goes to child
weakest = cset[i]; // make it the new weakest
cset.Destroy(weakest);
}
// mate the fastest 2 and fill the set with their children
while(cset.GetItemsInContainer() < csetsize)
cset.Add(new Classifier(&e,cset[0],cset[1]));
passtimer.stop();
scr << "----------------------------------------------------------" << endl;
scr << "Whole set: " << passtimer << endl;
scr << "----------------------------------------------------------" << endl;
// if sampling interval elapsed, or first pass, write time for this pass
// interval=1 min. will write max. 600 items / 10 hours,
// writes every slow pass, but only samples faster passes.
if(!passcount || (runtimer.split() >= 60))
{
timerfile << passtimer.trialtime() << endl;
runtimer.reset();
}
} // end while(1), each pass ends here
} // end evolveclassifiers
//--------------------------------------------------------------------------
// for each Classifier in finalset.dat, run it through 30 trials, and record
// the times to disk. One trial consists of 4 problems, same as when evolving.
void timetrials()
{
int trialcount = 30; // might allow user to change it
scr << "This will, for each Classifier in finalset.dat," << endl;
scr << "run it through 30 trials, and record the times to finalset.tim" << endl;
scr << "for export and analysis in Excel." << endl;
presskey(scr);
Classifier::beepon = Classifier::statusreport = FALSE; // no beep, no displays
Classifier::displaymode = Classifier::text;
ifstream infile("finalset.dat");
ofstream outfile("finalset.tim");
Environment e;
Classifier c(&e);
// all Classifiers get the same problem set, requiring
e.setproblem(trialcount * 2); // 60 problems for 30 passes. (60 actually makes 61)
int ccount = -1; // counts classifiers
while(infile >> c) // for each Classifier in the file,
{
ccount++;
scr << "Classifier #" << ccount << " constants = " << c << endl;
outfile << "c=" << c << endl;
for(int i = 0 ; i < trialcount ; i++) // 30 passes (problem sets)
{
c.rs.newruleset(); // empty trained rule set from previous pass
c.sw.reset(); // we want 1 time record for each pass
for(int j = 0 ; j < 4 ; j++) // number of problems to solve each pass
{
switch(j)
{
case 0: e.setproblem(i * 2); break; // first problem
case 1: e.setproblem(i * 2 + 1); break; // second problem
case 2: e.setproblem(i * 2); break; // first again
case 3: e.setproblem(i * 2); break; // and again
}
scr << "#" << ccount << " Trial " << i << " Problem " << j << " ";
int ch = c.run(300L); // 5 min limit
switch(ch)
{
case 27: // user aborted, entire run is finished
return;
case 'q': // user aborted 1 Classifier, no solution
scr << "No solution." << endl;
break;
}
} // end 4 problems in each pass
outfile << c.sw.totaltime << endl; // write time for this pass to disk
scr << " Total time = " << c.sw.totaltime << " seconds." << endl;
} // end 30 passes
scr << "----------------------------------------------------------" << endl;
outfile << "----------------------------------------------------------" << endl;
infile >> ws; // force fail if end of file
} // end while(infile >> c)
} // end timetrials
//--------------------------------------------------------------------------
// main
int main()
{
randomize();
string::set_case_sensitive(0);
string::set_paranoid_check(1);
string::initial_capacity(8);
TTime::PrintDate(FALSE);
scr.window(1,1,80,25); // set up text mode screen parameters
scr.clrscr();
scr << setclr(LIGHTGRAY);
initgraf(); // initialize graphics system, always VGAHI
restorecrtmode(); // but start out in text mode
Classifier::displaymode = Classifier::text; // set variable to match
Classifier::beepon = TRUE; // whether to use sound
Classifier::solutionpauselength = 1; // tone duration when solution found
Classifier::solutiontone = Classifier::LOWTONE; // frequency for PC speaker when solution found
Classifier::statusreport = TRUE; // whether to show variable values each pass
scr << endl << "Remember to disable Schedule+ before long unattended runs." << endl;
while(1)
{
restorecrtmode();
int choice;
do
{
scr << endl;
scr << "0 Exit" << endl;
scr << "1 Watch standard classifiers (all default values) learn" << endl;
scr << "2 Watch random classifiers (random values) learn" << endl;
scr << "3 Evolve classifier set" << endl;
scr << "4 Time trials, 30 runs each, elapsed times auto-saved to disk" << endl;
scr << endl << "Enter choice: ";
cin >> choice;
scr << endl;
}
while((choice < 0) || (choice > 4));
switch(choice)
{
case 0:
closegraph();
return(0);
case 1:
watchsingle(FALSE);
break;
case 2:
watchsingle(TRUE);
break;
case 3:
evolveclassifiers();
break;
case 4:
timetrials();
break;
}
} // end while(1)
} // end main()
/* library.cpp 3/13/03 This file is part of the CLASSIFY project. Copyright (C)1995-99 Steven Whitney. Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY. Initially published by http://25yearsofprogramming.com. Library source for classify.cpp. It pulls in routines needed by the main program. */ #include <alloc.h> #include <math.h> #include <graphics.h> #include <classlib\arrays.h> #pragma hdrstop #include "c:\bcs\my.h" #include "c:\bcs\mylib.cpp" #include "c:\bcs\library\stopwatc.cpp"
/* classify.h 1-24-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
class Classifier.
*/
#ifndef __CLASSIFY_H
#define __CLASSIFY_H
#include <math.h>
#include <graphics.h>
#include <classlib\arrays.h>
#pragma hdrstop
#include "c:\bcs\my.h"
#include "problem.h"
#include "environ.h"
#include "bboard.h"
#include "rule.h"
#include "ruleset.h"
#include "c:\bcs\library\stopwatc.h"
////////////////////////////////////////////////////////////////////////////
// a Classifier encapsulates an entire classifier system.
// It can receive input from an external environment, generate and emit
// a response, and receive feedback from the environment.
class Classifier
{
public:
// e=0 allows this as default constructor, but for real use it must have an environment.
Classifier(Environment* e = 0, BOOL initrandom = FALSE);
Classifier(Environment*,Classifier*,Classifier*); // mating constructor
Classifier(const Classifier&); // copy constructor
~Classifier(); // destructor
BOOL operator == (const Classifier& other) const;
BOOL operator < (const Classifier& other) const; // compares performance
BOOL operator > (const Classifier& other) const; // compares performance
friend ostream& operator << (ostream& os, const Classifier&); // write constants
friend istream& operator >> (istream& is, Classifier&); // reads constants
static string helptext(); // text for user help screen
int run(long timelimit = MAXLONG); // start classifier working on a problem
RuleSet rs;
Stopwatch sw; // for tracking this Classifier's performance
// these settings remain in effect for each new classifier
static enum dispmodes { text, graphics } displaymode; // report as text or graphics
static BOOL statusreport; // whether to show variable values each pass
static BOOL beepon; // whether to use sound
static int solutionpauselength; // tone duration when solution found
static enum tones { LOWTONE = 500, HIGHTONE = 12000 } solutiontone; // tone frequency
protected:
Environment* env; // environment it interacts with
long iterations; // passes in run(), is a member for status reports
// capitalized = inheritable members:
int INITIALBID; // bidding "currency" a rule starts out with
int SUCCESSREWARD; // payoff when solution is achieved.
int PASSBACK; // fraction paid to source rule
int REPRODUCERATE; // 1 offspring produced every 1 in this many iterations
int MUTATIONRATE; // a random bit-flip every 1 in this many offspring
int RULELIMIT; // maximum allowed number of rules
int RULECOUNT; // starting number of rules in the system
int MAXMSG; // maximum # messages on bulletin board at one time
// the bulletinboard could be a separate object, including post()
TArrayAsVector<Bulletinboardentry> bb; // the bulletin board
void initialize(); // set constants, in one place for any constructor
void mutate(BOOL changeall = FALSE); // mutate constants
void sense(); // get problem from environment
void post(const string& message, Rule* sourcerule); // post bb message
string statustext(); // show status in text mode
void statusgraphics(); // show status in graphics mode
};
//----------------------------------------------------------------------------
// end class Classifier
//////////////////////////////////////////////////////////////////////////////
#endif // CLASSIFY.H
/* classify.cpp 1-24-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
Code for class Classifier.
*/
#include "classify.h"
////////////////////////////////////////////////////////////////////////////
// global data
////////////////////////////////////////////////////////////////////////////
extern constream scr; // console screen
////////////////////////////////////////////////////////////////////////////
// Classifier
//----------------------------------------------------------------------------
// these static members must be defined here. and give them values so we know
// they all are at least initialized to something valid.
BOOL Classifier::statusreport = TRUE;
enum Classifier::dispmodes Classifier::displaymode = Classifier::text;
BOOL Classifier::beepon = TRUE;
int Classifier::solutionpauselength = 1;
enum Classifier::tones Classifier::solutiontone = Classifier::LOWTONE;
////////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------
// constructor. For the constants, it can either use the
// default values from initialize() or generate random values.
// initrandom = whether to assign random values to all constants.
Classifier::Classifier(Environment* e, BOOL initrandom) : bb(10,0,10)
{
// if e == 0 (default value), it's useless, but not an error. (default ctor for array use)
env = e; // find the environment object
initialize(); // initialize all constants, and build a ruleset
if(initrandom) // now override most constants,
mutate(TRUE); // and also generate a new ruleset
}
//--------------------------------------------------------------------------
// copy constructor
Classifier::Classifier(const Classifier& other) : bb(other.MAXMSG,0,other.MAXMSG)
{
env = other.env;
initialize(); // initialize all variables
// now override most of them
INITIALBID = other.INITIALBID;
SUCCESSREWARD = other.SUCCESSREWARD;
PASSBACK = other.PASSBACK;
REPRODUCERATE = other.REPRODUCERATE;
MUTATIONRATE = other.MUTATIONRATE;
RULELIMIT = other.RULELIMIT;
RULECOUNT = other.RULECOUNT;
MAXMSG = other.MAXMSG;
// for now, the other's rules are NOT copied. maybe they should be.
rs.newruleset(RULECOUNT,RULELIMIT,INITIALBID,MUTATIONRATE); // fill with random starting rules
// this will copy the other's rules
// int count = other.rs.GetItemsInContainer(); // how many rules to copy
// for(int i = 0 ; i < count ; i++) // copy all the other's rules
// rs.Add(*(other.rs[i]));
} // copy constructor
//--------------------------------------------------------------------------
// mating constructor. each constant is inherited from one or the other parent
// could make environment inherited, especially if Classifiers can ever
// operate in more than one. But it turns "both parents null" into an
// unrecoverable error.
Classifier::Classifier(Environment* e, Classifier* first, Classifier* second) : bb(10,0,10)
{
if(!e)
graborts("No environment.");
env = e; // find the environment object
initialize(); // initialize all variables
if(!first || !second) // one or both pointers is null
{
if(!first && !second) // both null. standard values at least
{ // guarantee a valid Classifier
// standard values already assigned in initialize()
}
else // only one is null. use the valid parent as both parents
{
if(!first)
first = second;
else
second = first;
}
}
// override variables with inherited data
INITIALBID = (random(2) ? first->INITIALBID : second->INITIALBID);
SUCCESSREWARD = (random(2) ? first->SUCCESSREWARD : second->SUCCESSREWARD);
PASSBACK = (random(2) ? first->PASSBACK : second->PASSBACK);
REPRODUCERATE = (random(2) ? first->REPRODUCERATE : second->REPRODUCERATE);
MUTATIONRATE = (random(2) ? first->MUTATIONRATE : second->MUTATIONRATE);
RULELIMIT = (random(2) ? first->RULELIMIT : second->RULELIMIT);
RULECOUNT = (random(2) ? min(first->RULECOUNT,RULELIMIT)
: min(second->RULECOUNT,RULELIMIT));
MAXMSG = (random(2) ? first->MAXMSG : second->MAXMSG);
if(!random(2)) // high rate because testing them is so slow
mutate();
} // mating constructor
//--------------------------------------------------------------------------
// destructor
Classifier::~Classifier()
{
bb.Flush(); // necessary?
}
//--------------------------------------------------------------------------
// a Classifier is equal only to itself
BOOL Classifier::operator == (const Classifier& other) const
{
return(this == &other);
}
//--------------------------------------------------------------------------
// less than refers to its fitness: a weak Classifier is slower
BOOL Classifier::operator < (const Classifier& other) const
{
return(sw.totaltime > other.sw.totaltime);
}
//--------------------------------------------------------------------------
// greater than refers to its fitness: a strong Classifier is faster
BOOL Classifier::operator > (const Classifier& other) const
{
return(sw.totaltime < other.sw.totaltime);
}
//--------------------------------------------------------------------------
// write constants to a stream
ostream& operator << (ostream& os, const Classifier& c)
{
os << c.INITIALBID << " " << c.SUCCESSREWARD << " " << c.PASSBACK << " ";
os << c.REPRODUCERATE << " " << c.MUTATIONRATE << " " << c.RULECOUNT << " ";
os << c.RULELIMIT << " " << c.MAXMSG;
return(os);
}
//--------------------------------------------------------------------------
// get constants from a stream
istream& operator >> (istream& is, Classifier& c)
{
is >> c.INITIALBID >> c.SUCCESSREWARD >> c.PASSBACK >> c.REPRODUCERATE;
is >> c.MUTATIONRATE >> c.RULECOUNT >> c.RULELIMIT >> c.MAXMSG;
c.RULECOUNT = min(c.RULECOUNT,c.RULELIMIT); // prevent error
// c already has a rule set, based on old values of the variables just read in,
// so delete & rebuild the rule set for the new values.
c.bb.Flush(); // just in case, but it should be empty
c.rs.newruleset(c.RULECOUNT,c.RULELIMIT,c.INITIALBID,c.MUTATIONRATE);
c.sw.reset(); // any time in the timer is meaningless for the new one
// other members reset, just because in principle >> should set everything
// you must not change!: env
c.iterations = 0; // although it's reset each run() pass anyway
return(is);
} // >>
//--------------------------------------------------------------------------
// help options, get text for display
string Classifier::helptext()
{
ostrstream os;
os << "While running:" << endl;
os << endl;
os << "If sound is enabled, a tone is produced when a solution is found." << endl;
os << endl;
os << "<ESC> Quit" << endl;
os << "<space> Pause" << endl;
os << "0,1,...9 Set tone duration (seconds) when solution found" << endl;
os << "B Toggle use of sound on/off" << endl;
os << "D Toggle Display of status report each iteration" << endl;
os << "G T GRAPHICS or TEXT mode for status report" << endl;
os << "H Show this Help screen" << endl;
os << "P 1 Peek at status screen" << endl;
os << endl;
os << ends;
string s(os.str());
delete[] os.str();
return(s);
} // helptext
//--------------------------------------------------------------------------
// mutate one or all of the constants by giving them random values.
// This will cover a large range of values quickly, and is simple to achieve.
// Creeping variables by +/- 1 point retraces same values repeatedly,
// (a Brownian random walk), and very inefficient.
void Classifier::mutate(BOOL changeall)
{
int inheritables = 8; // # of inheritable members, for easy changing
for(int i = 0 ; i < inheritables ; i++)
{
// if setting all, do them in order. otherwise choose one at random.
int choice = (changeall ? i : random(inheritables));
switch(choice)
{
case 0:
INITIALBID = random(98) + 2; // must be < 100
break;
case 1:
SUCCESSREWARD = random(5000) + 3;
break;
case 2:
PASSBACK = random(3000) + 1;
break;
case 3:
REPRODUCERATE = random(10) + 1;
break;
case 4:
MUTATIONRATE = random(3) + 1;
break;
case 5:
// when limit was based on count, it allowed upward ratcheting
// because limit couldn't reduce until count did first.
// limit should be more significant to performance, anyway.
RULELIMIT = random(300) + 2;
RULECOUNT = min(RULECOUNT,RULELIMIT); // count == limit is ok
break;
case 6:
RULECOUNT = random(RULELIMIT) + 1; // 1 to RULELIMIT
break;
case 7:
MAXMSG = random(20) + 3;
break;
}
if(!changeall) // if only changing one, quit after first pass
break;
}
// the Classifier's copies of these constants exist so they can be experimented with.
// transfer the changed info TO the actual rule set that will use them.
// and create a new rule set using them.
rs.newruleset(RULECOUNT,RULELIMIT,INITIALBID,MUTATIONRATE);
} // mutate
//--------------------------------------------------------------------------
// initialize in one place variables that are the same for any constructor.
void Classifier::initialize()
{
// bidding "currency" a rule starts out with
// absolute min=1: if no bid, it can't enter lottery!
// integer math: 1 - 1/2 = 1 - 0 = 1 allows preserving minimum
// a new rule only gets this many passes to get chosen in lottery
INITIALBID = 76;
// payoff when solution is achieved.
SUCCESSREWARD = 34;
// fraction paid to source rule = (1 / PASSBACK) times its bid.
PASSBACK = 941;
// 1 Rule child produced every 1 in this many iterations.
// if values go to 1, change the meaning to X offspring each iteration.
// then range = 1 to 10, random value = random(10) + 1
REPRODUCERATE = 1;
// a random mutation every 1 in this many Rule offspring
MUTATIONRATE = 1;
// maximum number of rules allowed
RULELIMIT = 28;
// starting number of rules, <= limit
RULECOUNT = 20;
// maximum # messages on bulletin board at one time
MAXMSG = 7;
// the Classifier's copies of these constants exist so they can be experimented with.
// transfer the changed info TO the actual rule set that will use them.
// and create a new rule set using them.
rs.newruleset(RULECOUNT,RULELIMIT,INITIALBID,MUTATIONRATE);
} // initialize
//--------------------------------------------------------------------------
// display the rules, bulletin board, and problem/solution strings.
// Don't incorporate graphics() display here: later, might want text
// status to go to H89, with graphics simultaneously to Gateway display.
string Classifier::statustext()
{
if(!env)
graborts("No environment.");
int bbitems = bb.GetItemsInContainer();
int bbcounter = 0;
long bidtotal = 0L;
ostrstream os;
os << "# Stimulus Response Bid MsgBoard Problem Solution\n";
os << "----- ------------------- ---------- --------- -------- --------\n";
// there should always be more rules than board messages. use "rules" for the loop
// only show (and sum bidtotal for) first 20. Finding the strongest 20 is more
// complicated and time-consuming than it's worth. If you want to see more,
// you must write them to disk and then go look.
int rsitems = min(rs.GetItemsInContainer(),20u);
for(int i = 0 ; i < rsitems ; i++ , bbcounter++)
{
// winner? else bidder? else neither
char ch = rs[i]->wasawinner ? 'W' : rs[i]->isabidder ? '-' : ' ';
os << setw(4) << i << ": " << rs[i]->stimulus << " " << rs[i]->response;
os << " " << ch << " " << setw(10) << rs[i]->bid;
bidtotal += (long)(rs[i]->bid);
if(bbcounter < bbitems) // sensor-posted messages are prefixed with 's'
os << " " << ((bb[bbcounter].responsible)?' ':'s') << bb[bbcounter].msg;
else
os << " ";
os << " " << env->getproblem(); // show problem
os << " " << env->getsolution() << endl; // show target solution
}
os << "Child: " << setw(10) << rs.childcount << ". Total: " << setw(10) << bidtotal;
os << ". Iterations: " << iterations;
os << ". Rules: " << rs.GetItemsInContainer() << endl;
os << "------------------------------------------------------------------------\n";
os << ends;
string s(os.str());
delete[] os.str();
return(s);
} // statustext
//--------------------------------------------------------------------------
// graphically display the relative strengths of rules
// note: the oldest (longest surviving) rules are on the left;
// rules are born on the right.
void Classifier::statusgraphics()
{
clearviewport();
int x, y;
int ymax = getmaxy(); // bottom of screen
int rscount = rs.GetItemsInContainer();
for(int i = 0 ; i < rscount ; i++)
{
x = i; // can use this to adjust spacing
if(x >= 640) // no point in graphing beyond right edge
break;
y = ymax - (rs[i]->bid / 70); // 0-32767 fits on screen
// y = ymax - (rs[i]->bid / 128); // only bottom half screen, but might be fast:
// needs only a right-shift.
setcolor(rs[i]->wasawinner ? WHITE : rs[i]->isabidder ? RED : LIGHTBLUE);
line(x,ymax,x,y);
}
} // statusgraphics
//--------------------------------------------------------------------------
// post a message on the bulletin board.
// rule-posted messages scroll off the board. sensor-posted messages do not.
// Leaving them alone ensures that sensors are always present to be responded to.
// This is to prevent rules just cycling off each other, forgetting the question
// and to allow sensor responders to develop
void Classifier::post(const string& message, Rule* sourcerule)
{
// There is no scrolling anymore:
// if(bb.GetItemsInContainer() >= MAXMSG) // need to scroll?
// {
// int count = bb.GetItemsInContainer();
// for(int i = 0 ; i < count ; i++)
// if(bb[i].responsible) // (if no responsible, it was sensor-posted)
// {
// bb.Destroy(i); // destroy first (oldest) rule-posting
// break; // only 1 scrolls off
// }
// }
bb.Add(Bulletinboardentry(message,sourcerule)); // Add the new element
// if(sourcerule) // might be sensor posted
// sourcerule->locked = TRUE; // prevent deletion while its posted
// lock all rules with bb postings
// int count = bb.GetItemsInContainer();
// for(int i = 0 ; i < count ; i++)
// if(bb[i].responsible) // (if no responsible, it was sensor-posted)
// bb[i].responsible->locked = TRUE;
} // post
//--------------------------------------------------------------------------
// post bulletin board messages that reflect the current outside world state.
// bb no longer flushed first. you can always add sensor postings.
// This version just posts the "question".
void Classifier::sense()
{
post(env->getproblem(),0); // the original question isn't attributed to a rule
} // sense
//--------------------------------------------------------------------------
// start the classifier working on a problem. Continues until it succeeds or
// user stops it, or time limit is exceeded.
// Keep a watch on when rules are locked/unlocked. Locking is supposed to protect certain
// rules from deletion when a deletion is otherwise ok, AND protect the array from
// my own mistakes while rearranging order of execution! So a rule should always be
// protected from deletion while its data is required, and unlocked as soon as it's not.
// returns:
// 1 = problem solved, (not now used)
// currently, only the following are ever tested for upon return:
// Q = User quit this Classifier's run only, (continue with others)
// 27 = User Quit pgm.
int Classifier::run(long timelimit) // maximum number of seconds
{
if(!env)
graborts("No environment.");
int i;
int ch = 0; // set to 'p' to cause one initial peek regardless of mode
sw.start(); // start timer running before refilling rs
// refill to starting capacity with random rules (guesses), which may be more
// likely to find solution to a new problem than offspring of existing
// rules specialized for previous problem.
// still, the justification for this is somewhat questionable, though it is
// necessary because Sweep (upon prior exit) decimates rs.
rs.TopUp(FALSE,FALSE);
iterations = 0L;
bb.Flush(); // unnecessary, reminder that sense() doesn't do it
sense(); // start out by posting sensory state to bb
while(1)
{
//--------------------------------------------------------------------------
// user command section, unrelated to classifier operation
// at this point, ch may have a value that was set anywhere later in the loop
if(ch != 1) // solution found is highest priority
{
if(kbhit()) // last processed overrides
ch = tolower(getch());
if(sw.split() > timelimit) // auto-abort: took too long
ch = 'q';
}
// other options here could allow user entry of sensor-posted messages at any
// time. either pre-coded (e.g. 'h' means post a specific msg), or pause to prompt
// user for msg to post. example use = gas pipeline problem, requiring real time input
// of changing simulated conditions.
switch(ch)
{
case 0: // most passes
break;
case 27: // user exit or
case 'q': // Quit this Classifier only
sw.stop(); // stop timer so compare value is valid, fall through,
case 1: // solution was found, timer is already stopped earlier
bb.Flush();
rs.ZeroAllFlags(); // prevent leftover data causing problems next entry
rs.UnlockAll();
rs.Sweep(100); // delete the partial solutions rules (bid < 100),
// to free memory so other classifiers can build up
// & use their rule sets w/o running out of memory.
return(ch); // return ch so caller knows what to do
case '0': // set solutionfound pause duration
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
solutionpauselength = (int)ch - 0x30;
// hopefully, more tonelike & less of a click when duration is short.
solutiontone = solutionpauselength ? LOWTONE : HIGHTONE;
break;
case ' ': // user pause
if(displaymode == text)
scr << "Paused. Press any key...";
getch();
if(displaymode == text)
scr << endl;
break;
case 'b': // toggle sound
beepon = !beepon;
break;
case 'd': // auto-display status
statusreport = !statusreport;
break;
case 'g': // set status report to graphics format
statusreport = TRUE; // assume we want continuing display
if(displaymode == text)
setgraphmode(VGAHI);
displaymode = graphics;
break;
case 'h': // help
if(displaymode == graphics)
restorecrtmode(); // actual mode temporarily out of synch with displaymode
scr << helptext();
presskey(scr);
if(displaymode == graphics)
setgraphmode(VGAHI); // restore actual mode
break;
case 'p': // peek at status
// the peek is done later - where the bulletin board is valid.
break;
case 't': // set status report to text format
statusreport = TRUE; // assume we want continuing display
if(displaymode == graphics)
restorecrtmode();
displaymode = text;
break;
}
// DON'T reset ch here (see below)
//--------------------------------------------------------------------------
// subtracting ante used to be here.
//--------------------------------------------------------------------------
// generate new rule children for the next try at the problem.
// preferentially selects bidders as parents. But all we know
// is that they were bidders the last pass (maybe won't be this one).
// Adding the new rule may cause deletion of the weakest unlocked rule that
// isn't a source for another rule.
if(!random(REPRODUCERATE))
rs.Addchild(TRUE);
// reset wasawinner, isabidder, and source for all rules
rs.ZeroAllFlags();
//--------------------------------------------------------------------------
// identify which rules will be bidders in the upcoming lotteries.
// their source is recorded, and isabidder set TRUE.
// #error why not also lock sources here?
while(!rs.MarkBidders(bb) && bb.GetItemsInContainer())
{
// if there were no bidders, RuleSet adds some new Rules,
// one tailored to respond to each of the current bb posting(s?).
int N = bb.GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
{
rs.AddCustom(bb[i].msg);
}
}
// we know who wants to post. this bb no longer needed.
bb.Flush();
//--------------------------------------------------------------------------
// auction off all the bulletin board slots allowed for rule postings.
for(i = 0 ; i < MAXMSG ; i++)
{
Rule* winner = rs.lottery(TRUE); // returns zero if no winner, no bids
if(winner)
{
// for debugging, a serious error if it occurs
if(!winner->source)
graborts("Error: winner->source is null.");
// pay source when it wins the right to post (now)
winner->paysource(PASSBACK);
// lottery() should not set this. lottery is also used for other purposes.
winner->wasawinner = TRUE; // remember we won
winner->isabidder = FALSE; // can't win again
}
else // no (or no more) winners
break;
}
//
rs.UnlockAll();
// all winners post their msgs, and get locked
rs.PostNewMessages(bb);
//----------------------------------------------------------------------
// effector actions probably would go here because they carry out the msgs just posted,
// and the result (the feedback from those actions) determines the grade.
//----------------------------------------------------------------------
// Feedback from environment. response is judged for correctness.
// version 1, ALL active rules get credit for the best grade that any one achieves.
// seems needlessly inefficient, since we DO know which rule was responsible.
// int grade = 0;
// int N = bb.GetItemsInContainer();
// for(int i = 0 ; i < N ; i++)
// {
// if(bb[i].responsible) // only rule-posted msgs eligible!
// grade = max(grade,env->score(bb[i].msg)); // (int)0-100 percent
// }
// if(grade == 100) // found the exact solution (100%)
// {
// sw.stop(); // stop timer
// rs.PayAllWinners(SUCCESSREWARD); // pay winner
// }
// else // reward for partial correctness is an absolute number,
// { // not a percentage of successreward.
// // "price support": a rule's bid isn't allowed to fall below a minimum.
// // The minimum depends on how "right" the rule's response is.
// // minimum = 0-100, set bid to match it. no passback here, for now.
// rs.SupportAllWinners(grade);
// }
// version 2: reward individual rules by how well they did
// we still need to know the best grade that any rule gets, for later.
// i.e. did any rule post the exact solution? only IT gets rewarded, but
// other actions later depend on solution having been found.
int grade = 0;
int N = bb.GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
{
if(!bb[i].responsible) // ignore sensor postings (won't be any)
continue;
int thisgrade = env->score(bb[i].msg); // grade for this posting only, 0-100 percent
grade = max(grade,thisgrade); // highest so far?
if(thisgrade == 100) // found the exact solution (100%)
{
sw.stop(); // stop timer
bb[i].responsible->AdjustBid(SUCCESSREWARD); // pay poster
}
else
{
// reward for partial correctness is an absolute number,
// not a percentage of successreward.
// "price support": a rule's bid isn't allowed to fall below a minimum.
// The minimum depends on how "right" the rule's response is.
// minimum = 0-100, set bid to match it. no passback here, for now.
bb[i].responsible->SupportBid(thisgrade);
}
}
//----------------------------------------------------------------------
// to show you can zero out the sources here because we have paid them.
rs.ZeroSources();
iterations++; // iteration is complete here, except statusreport & grading
// get sensor input for next iteration. it is good that bb only had rule postings above,
// for grading, so sensor postings couldn't accidentally post the right answer.
sense();
//--------------------------------------------------------------------------
// status report section, doesn't affect any classifier system variables
if(statusreport) // status report EACH iteration
{
if(displaymode == text)
{
// scr.clrscr(); // unstable, garbage output when solution found.
// scr << setxy(1,1); // good, but also somewhat unstable.
scr << statustext(); // display rules, bulletin board, etc.
}
else
statusgraphics(); // graphic display of rule strengths
}
// if we're not already showing status, or it's only graphic, allow 1 peek
if(!statusreport || (displaymode == graphics))
if(ch == 'p') // TEXT status report THIS iteration only
{
if(displaymode == graphics)
restorecrtmode();
scr << statustext();
if(displaymode == graphics)
{
presskey(scr);
setgraphmode(VGAHI);
}
}
ch = 0; // ok to reset: we're through with it
//--------------------------------------------------------------------------
// actions to take if exact solution found
// individual rules have already been processed and rewarded.
// this section is only for general actions.
// it returns when the solution is found. to fully train, you need to solve
// a problem more than once to allow passback to work. if you want complete
// training, come back again later.
if(grade == 100) // perfect score = exact solution found
{
if(displaymode == text) // report appears while beep sounds
{
// iterations gives clue how many rules chained to solve problem
scr << "\a"; // a click if !beepon
scr << "Solution found! " << iterations << " iterations. ";
scr << sw.trialtime() << " seconds.\n";
}
if(displaymode == graphics) // (in text mode, it would just restart rapid scrolling)
statusreport = TRUE; // resume automatic status display
if(beepon) // report the success
{
sound(solutiontone);
sleep(solutionpauselength); // pause
nosound();
}
ch = 1; // force return in switch() at loop start
}
} // end while(1)
} // run
//--------------------------------------------------------------------------
// end class Classifier
////////////////////////////////////////////////////////////////////////////
/* bboard.h 1-12-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
*/
#ifndef __BBOARD_H
#define __BBOARD_H
#include "c:\bcs\my.h"
class Rule; // forward ref.
////////////////////////////////////////////////////////////////////////////
// a bulletinboardentry is an entry on the bulletin board
// for now, the entry is an 8-letter string of chars for ease of visual review
class Bulletinboardentry
{
public:
Bulletinboardentry();
Bulletinboardentry(const string& m, Rule* r);
BOOL operator == (const Bulletinboardentry& other) const;
string msg;
Rule *responsible; // we must know which rule posted the msg, to reward it.
// OR, 0 = a sensor posted msg
};
//----------------------------------------------------------------------------
// end class Bulletinboardentry
//////////////////////////////////////////////////////////////////////////////
#endif // BBOARD.H
/* bboard.cpp 1-6-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
*/
#include "bboard.h"
//////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
Bulletinboardentry::Bulletinboardentry()
{
responsible = 0;
}
//----------------------------------------------------------------------------
Bulletinboardentry::Bulletinboardentry(const string& m, Rule* r)
{
msg = m;
responsible = r;
}
//----------------------------------------------------------------------------
// &other == this would be faster, but believe choice of which to use here
// isn't particularly important. Doesn't affect scrolling.
BOOL Bulletinboardentry::operator == (const Bulletinboardentry& other) const
{
return((msg == other.msg) && (responsible == other.responsible));
}
//////////////////////////////////////////////////////////////////////////////
/* environ.h 1-5-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
*/
#ifndef __ENVIRON_H
#define __ENVIRON_H
#include <classlib\arrays.h>
#pragma hdrstop
#include "c:\bcs\my.h"
#include "problem.h"
////////////////////////////////////////////////////////////////////////////
// the Environment is the entity the classifier interacts with.
// It poses the problem (creates the problem situation), can transmit the
// state of the environment to the classifier, receive the classifier's
// responses, and give the classifier's responses graded scores as to
// whether, or how closely, it solved the problem.
// This class should be envisioned as a template, possibly later to be
// used as a base class. That is, there are certain functions that any
// environment will have to have or be able to do.
class Environment
{
public:
Environment();
~Environment();
void flushproblems(); // empty problems array
void addproblem(); // generate a new problem & add to list
void setproblem(int index); // set current to a specific problem
int problemstodisk();
int problemsfromdisk();
const string& getproblem(); // allows viewing, prevents changes
const string& getsolution(); // allows viewing, prevents changes
int score(const string& proposed); // how nearly correct proposed is
protected:
TIArrayAsVector<Problem> problems; // problems for classifier to solve
Problem* current; // the problem currently presented
BOOL fromdisk; // whether to load problems and solutions from disk
};
#endif // ENVIRON.H
/* environ.cpp 1-5-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
*/
#include "environ.h"
//////////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------
// constructor
Environment::Environment() : problems(20,0,1)
{
current = 0;
fromdisk = FALSE;
addproblem(); // get starting problem and its solution
} // Environment constructor
//--------------------------------------------------------------------------
// destructor
Environment::~Environment()
{
// problemstodisk(); // write to disk before destroying
problems.Flush(TShouldDelete::Delete);
} // Environment destructor
//--------------------------------------------------------------------------
// empty problems array and restart with 1 problem
void Environment::flushproblems()
{
// problemstodisk(); // write to disk first
problems.Flush(TShouldDelete::Delete); // empty the array
addproblem(); // start with 1 problem
} // flushproblems
//--------------------------------------------------------------------------
// save entire problem set to disk
int Environment::problemstodisk()
{
ofstream outfile("problems.dat");
int count = problems.GetItemsInContainer();
for(int i = 0 ; i < count ; i++)
outfile << *(problems[i]) << endl;
return(count);
} // rulestodisk
//--------------------------------------------------------------------------
// load rule set from disk
int Environment::problemsfromdisk()
{
problems.Flush(TShouldDelete::Delete); // empty it before filling
Problem p;
ifstream infile("problems.dat");
while(infile >> p)
problems.Add(new Problem(p));
int count = problems.GetItemsInContainer();
if(!count)
graborts("File PROBLEMS.DAT corrupt or not found. Repair and rerun.");
return(count);
} // rulesfromdisk
//--------------------------------------------------------------------------
// allow access to the problem string, but prevent changes to it
const string& Environment::getproblem()
{
return(current->problem);
} // getproblem
//--------------------------------------------------------------------------
// allow access to the solution string, but prevent changes to it
const string& Environment::getsolution()
{
return(current->solution);
} // getsolution
//--------------------------------------------------------------------------
// set current to a specific index. If the index isn't valid, it adds
// problems until it is. This allows a Classifier to request re-presentation
// of a previous problem.
void Environment::setproblem(int index)
{
while(problems.GetItemsInContainer() <= index) // index won't be valid
problems.Add(new Problem); // add elements until it is
current = problems[index]; // set current problem
} // setproblem
//--------------------------------------------------------------------------
// generate a new problem, add it to the list, and make it the current problem
void Environment::addproblem()
{
current = new Problem;
problems.Add(current);
} // addproblem
//--------------------------------------------------------------------------
// determine how nearly correct a proposed solution is. It implements
// "operant conditioning": system is rewarded for steps in the right direction
// toward solution.
// This may be a lot more difficult for more complicated tasks.
// The environment would really only be able to return its current state,
// and the Classifier would have to determine if that's what it wanted.
// returns: 0-100 representing the percent that the proposed solution is correct.
// This version compares a response string with the solution[] string.
int Environment::score(const string& proposed)
{
int correct = 0;
int maxlen = min(proposed.length(),current->solution.length()); // # of chars in shortest
for(int i = 0 ; i < maxlen ; i++)
if(proposed[i] == current->solution[i])
correct++;
if(maxlen == 0) // either proposed or solution was empty
return(0); // prevent divide by zero
int score = 100 * correct / current->solution.length(); // determine the score
if(score == 100) // if solution was found,
current->solved = TRUE;
return(score); // percent correct
} // score
//--------------------------------------------------------------------------
// end class Environment
//////////////////////////////////////////////////////////////////////////////
/* problem.h 1-6-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
*/
#ifndef __PROBLEM_H
#define __PROBLEM_H
#include "c:\bcs\my.h"
////////////////////////////////////////////////////////////////////////////
// a Problem encapsulates a problem string with its solution string,
// so they can be stored in an array.
// some other functions from Environment might be moved here: score()
class Problem
{
public:
Problem();
Problem(const string& p, const string& s);
Problem(const Problem&);
BOOL operator == (const Problem& other) const;
friend ostream& operator << (ostream& os, const Problem&);
friend istream& operator >> (istream& is, Problem&);
string problem; // the problem for the classifier to solve
string solution; // the correct answer to problem
BOOL solved; // whether this one has ever been solved
// currently not used
protected:
};
#endif // problem.h
/* problem.cpp 1-4-99
This file is part of the CLASSIFY project.
Copyright (C)1995-99 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
*/
#include "problem.h"
//--------------------------------------------------------------------------
// default constructor
Problem::Problem()
{
for(int i = 0 ; i < 8 ; i++)
{
problem += (random(2) ? '0' : '1');
solution += (random(2) ? '0' : '1');
}
solved = FALSE;
}
//--------------------------------------------------------------------------
// constructor
Problem::Problem(const string& p, const string& s)
{
problem = p;
solution = s;
solved = FALSE;
}
//--------------------------------------------------------------------------
// copy constructor
Problem::Problem(const Problem& other)
{
problem = other.problem;
solution = other.solution;
solved = other.solved;
}
//--------------------------------------------------------------------------
// I've not used &other == this because it might be desirable to judge 2
// DIFFERENT Problems to be equal, such as for preventing duplicates in an array.
BOOL Problem::operator == (const Problem& other) const
{
return( (problem == other.problem) &&
(solution == other.solution) &&
(solved == other.solved) );
}
//--------------------------------------------------------------------------
// write a problem to any ostream
ostream& operator << (ostream& os, const Problem& p)
{
os << p.problem << " " << p.solution << " " << (p.solved ? "YES" : "NO");
return(os);
}
//--------------------------------------------------------------------------
// read a problem from an istream
istream& operator >> (istream& is, Problem& p)
{
string solved; // solved is irrelevant on input
// and is text in the file, anyway
p.solved = FALSE; // but ensure it gets set
is >> p.problem >> p.solution >> solved;
return(is);
}
//--------------------------------------------------------------------------
// end class Problem
The rest of this program's code, for the Rule and RuleSet classes, is on the next page.
|
|
|
|