Česky
Kamil Dudka

Non-Uniform CA Designer (C++, GAlib, Boost)

File detail

Name:DownloadCaDesigner.cpp [Download]
Location: nucad > src
Size:18.0 KB
Last modification:2009-07-12 01:46

Source code

/*
 * Copyright (C) 2009 Kamil Dudka <xdudka00@stud.fit.vutbr.cz>
 *
 * This file is part of nucad (Non-Uniform CA Designer).
 *
 * nucad is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * nucad 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 nucad.  If not, see <http://www.gnu.org/licenses/>.
 */
 
/**
 * @file CaDesigner.cpp
 * Implementation of GaCaRules, GaCaRulesSet and class CaDesigner with its base
 * classes and observers.
 */
 
#include "config.h"
#include "CaDesigner.h"
#include "Color.h"
 
#ifndef GA_PARTIAL_RESULTS
#   define GA_PARTIAL_RESULTS 0
#endif
 
#ifndef BUILDING_DOX
#   if GA_PARTIAL_RESULTS
#      include <fcntl.h>
#      include <unistd.h>
#   endif
 
#   include <iomanip>
#   include <list>
#   include <math.h>
#   include <set>
 
#   include <boost/lexical_cast.hpp>
 
#   include <ga/GA1DBinStrGenome.h>
#   include <ga/GASimpleGA.h>
#   include <ga/GAStatistics.h>
#endif // BUILDING_DOX
 
#if 0
#include <ga/GASStateGA.h>
#include <ga/GAIncGA.h>
#include <ga/GADemeGA.h>
#endif
 
/**
 * Change this typedef to use another GA algorithm. Tested with GASimpleGA,
 * GASteadyStateGA, GAIncrementalGA and GADemeGA.
 */
typedef GASimpleGA TGeneticAlgorithm;
 
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// GaCaRules implementation
#ifndef BUILDING_DOX
struct GaCaRules::Private {
    size_t size;
    GABinaryString bs;
 
    Private(unsigned size_):
        size(size_),
        bs(size_*size_*RULE_WIDTH)
    {
    }
};
#endif
 
GaCaRules::GaCaRules():
    d(new Private(1))
{
    // dummy constructor used by GaCaRulesCmpDecorator
}
 
GaCaRules::GaCaRules(const std::string &str):
    d(new Private(1))
{
    // guess CA size
    size_t len = str.size();
    size_t caSize = static_cast<size_t>(sqrt(
                static_cast<float> (len / RULE_WIDTH)));
 
    if (caSize*caSize*RULE_WIDTH != len)
        // unexpected size of input
        throw std::runtime_error("GaCaRules::GaCaRules: input error (not handled)");
 
    // deserialize
    d->size = caSize;
    d->bs.resize(len);
    for (unsigned i = 0; i < len; ++i)
        d->bs.bit(i, boost::lexical_cast<bool>(str[i]));
}
 
GaCaRules::GaCaRules(size_t size, const GABinaryString &bs):
    d(new Private(size))
{
    // deep copy
    d->bs.copy(bs);
}
 
GaCaRules::GaCaRules(const GaCaRules &other):
    d(new Private(other.d->size))
{
    // deep copy
    d->bs.copy(other.d->bs);
}
 
GaCaRules::~GaCaRules() {
    delete d;
}
 
GaCaRules* GaCaRules::clone() const {
    return new GaCaRules(*this);
}
 
void GaCaRules::getRuleAtPos(Pos pos, TRule5N &rule) const {
    // FIXME: should it respect the CA_CYCLIC_NEIGHBORHOOD compile-time option?
    int offset = pos.row * d->size + pos. col;
    offset *= RULE_WIDTH;
    for (int i = 0; i < RULE_WIDTH; ++i) {
        assert(i + offset < d->bs.size());
        rule[i] = d->bs.bit(i + offset);
    }
}
 
size_t GaCaRules::size() const {
    return d->bs.size();
}
 
bool GaCaRules::operator[] (unsigned index) const {
    return d->bs.bit(index);
}
 
std::ostream& operator<< (std::ostream &str, const GaCaRules &data) {
    for (unsigned i = 0; i < data.size(); ++i)
        str << data[i];
    return str;
}
 
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CaDesigner implementation
#ifndef BUILDING_DOX
struct CaDesigner::Private {
    CaEvaluator               *evaluator;
    CaDesigner                *solver;                  ///< pointer to self
    float                     maxFitness;
    GA1DBinaryStringGenome    *genome;
    TGeneticAlgorithm         *ga;
    GaCaRulesSet              *resultSet;
 
    static float fitness(GAGenome &);
};
#endif
 
// protected
CaDesigner::CaDesigner (CaEvaluator *evaluator):
    d(new Private)
{
    d->evaluator = evaluator;
    d->solver = this;
    d->maxFitness = 0.0;
 
    const size_t caSize = evaluator->caSize();
    const size_t genomeSize = caSize * caSize * RULE_WIDTH;
    d->genome = new GA1DBinaryStringGenome(genomeSize, Private::fitness, d);
 
    d->ga = new TGeneticAlgorithm(*(d->genome));
    d->ga->populationSize(GA_POP_SIZE);
    d->ga->nGenerations(GA_INITAL_NGEN);
 
    d->resultSet = new GaCaRulesSet;
}
 
CaDesigner::~CaDesigner() {
    delete d->resultSet;
    delete d->ga;
    delete d->genome;
    delete d;
}
 
// it is not safe to call virtual methods in constructor
CaDesigner* CaDesigner::create (CaEvaluator *evaluator) {
    CaDesigner *obj = new CaDesigner(evaluator);
    obj->initialize();
    return obj;
}
 
const GAStatistics& CaDesigner::getStatistics() const {
    return d->ga->statistics();
}
 
int CaDesigner::getSolutionsCount() {
    return d->resultSet->size();
}
 
int CaDesigner::stopAtGeneration() const {
    return d->ga->nGenerations();
}
 
float CaDesigner::minFitness() {
    return d->ga->statistics().offlineMin();
}
 
float CaDesigner::avgFitness() {
    return d->ga->statistics().offlineMax();
}
 
float CaDesigner::maxFitness() {
    return d->maxFitness;
}
 
// protected
void CaDesigner::initialize() {
    GARandomSeed();
    d->maxFitness = 0.0;
    d->ga->initialize();
    d->ga->nGenerations(GA_INITAL_NGEN);
}
 
// protected
void CaDesigner::doStep() {
    GAGeneticAlgorithm &ga= *(d->ga);
    ga.step();
    if (ga.done()) {
        this->stop();
        GAStatistics stats= ga.statistics();
        const int generation = stats.generation();
        std::cerr << Color(C_YELLOW) << "--- Stopped by GAlib in "
            << generation << ". generation"
            << Color(C_NO_COLOR) << std::endl;
    }
}
 
float CaDesigner::Private::fitness(GAGenome &genome) {
    // static to non-static binding
    Private *d = reinterpret_cast<Private *>(genome.userData());
    CaEvaluator *evaluator = dynamic_cast<CaEvaluator *>(d->evaluator);
    CaDesigner *solver = dynamic_cast<CaDesigner *>(d->solver);
    const GABinaryString &bs= dynamic_cast<GABinaryString &>(genome);
    GaCaRulesSet *resultSet= dynamic_cast<GaCaRulesSet *>(d->resultSet);
 
    // compute fitness
    GaCaRules data(evaluator->caSize(), bs);
    float fitness = evaluator->eval(&data);
    if (d->maxFitness < fitness) {
        // compute nGenerations bonus
        float conFit = 1.0;
        for (unsigned i = 0; i < GA_PER_FIT_NGEN_POWER; ++i)
            conFit *= fitness;
        int total = static_cast<int>(conFit * GA_PER_FIT_NGEN);
        if (total < GA_INITAL_NGEN)
            total = GA_INITAL_NGEN;
        total += d->ga->statistics().generation();
        if (d->ga->nGenerations() < total)
            d->ga->nGenerations(total);
 
        // update maxFitness and notify observers
        d->maxFitness = fitness;
        solver->notify();
 
#if GA_PARTIAL_RESULTS
        // FIXME: use observer for this
        // FIXME: read fd as cmd-line argument
        const int fd = 3;
        if (-1 != fcntl(fd, F_GETFD)) {
            std::ostringstream str;
            str << data << std::endl;
            const std::string &text = str.str();
            // FIXME: repeat write(2) on EINTR
            write(fd, text.c_str(), text.size());
        }
#endif
    }
 
    if (1.0 <= fitness) {
        size_t last = resultSet->size();
        resultSet->add(data.clone());
        if (last != resultSet->size()) {
            // notify observers
            solver->notify();
 
            // FIXME: use observer for this
            std::cout << data << std::endl;
        }
    };
 
    return fitness;
}
 
 
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AbstractSubject implementation
#ifndef BUILDING_DOX
struct AbstractSubject::Private {
    typedef std::list<IObserver *> TContainer;
    TContainer container;
};
#endif
 
AbstractSubject::AbstractSubject():
    d(new Private)
{
}
 
AbstractSubject::~AbstractSubject() {
    // ATTENTION: Observers are not deleted on object destruction
    delete d;
}
 
void AbstractSubject::addObserver(IObserver *observer) {
    d->container.push_back(observer);
}
 
void AbstractSubject::notify() {
    Private::TContainer::iterator iter;
    for(iter=d->container.begin(); iter!=d->container.end(); iter++) {
        IObserver *observer = *iter;
        observer->notify();
    }
}
 
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AbstractProcess implementation
#ifndef BUILDING_DOX
struct AbstractProcess::Private {
    bool running;
    int steps;
};
#endif
 
AbstractProcess::AbstractProcess():
    d(new Private)
{
    d->running = false;
    d->steps = 0;
}
 
AbstractProcess::~AbstractProcess() {
    delete d;
}
 
void AbstractProcess::start() {
    for(d->running=true; d->running; d->steps++) {
        this->doStep();
        this->notify();
    }
}
 
void AbstractProcess::stop() {
    d->running = false;
}
 
void AbstractProcess::reset() {
    d->running = false;
    d->steps = 0;
    this->initialize();
}
 
int AbstractProcess::getStepsCount() {
    return d->steps;
}
 
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AbstractProcessWatched implementation
#ifndef BUILDING_DOX
struct AbstractProcessWatched::Private {
    static const long RATIO = CLOCKS_PER_SEC/1000L;
    clock_t start;
    long total;
    bool running;
    long currentElapsed() {
        clock_t diff = clock() - start;
        return diff/RATIO;
    }
};
#endif
 
AbstractProcessWatched::AbstractProcessWatched():
    d(new Private)
{
    d->total = 0;
    d->running = false;
}
 
AbstractProcessWatched::~AbstractProcessWatched() {
    delete d;
}
 
void AbstractProcessWatched::start() {
    d->start = clock();
    d->running = true;
    // Delegate to base
    AbstractProcess::start();
}
 
void AbstractProcessWatched::stop() {
    if (d->running) {
        d->running = false;
        d->total += d->currentElapsed();
    }
    // Delegate to base
    AbstractProcess::stop();
}
 
void AbstractProcessWatched::reset() {
    d->running = false;
    d->total = 0;
    // Delegate to base
    AbstractProcess::reset();
}
 
long AbstractProcessWatched::getTimeElapsed() {
    long total = d->total;
    if (d->running)
        total+= d->currentElapsed();
    return total;
}
 
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// GaCaRulesSet implementation
#ifndef BUILDING_DOX
struct GaCaRulesSet::Private {
    class GaCaRulesCmpDecorator: public GaCaRules {
        public:
            GaCaRulesCmpDecorator(GaCaRules *item):
                item_(item)
            {
            }
            void dispose() {
                delete item_;
            }
            virtual size_t size() const {
                return item_->size();
            }
            virtual bool operator[] (unsigned index) const {
                return item_->operator[] (index);
            }
            virtual GaCaRules* clone() const {
                return item_->clone();
            }
            bool operator< (const GaCaRulesCmpDecorator &other) const {
                for(unsigned i=0; i<size(); i++) {
                    if (!this->operator[](i) && other[i])
                        return true;
                    else if (this->operator[](i) && !other[i])
                        return false;
                }
                return false;
            }
        private:
            GaCaRules *item_;
    };
    typedef std::set<GaCaRulesCmpDecorator> TSet;
    TSet set;
 
    void add(GaCaRules *item) {
        GaCaRulesCmpDecorator comparableItem(item);
        if (set.end()==set.find(comparableItem))
            set.insert(comparableItem);
        else
            comparableItem.dispose();
    }
};
#endif // BUILDING_DOX
 
GaCaRulesSet::GaCaRulesSet():
    d(new Private)
{
}
 
GaCaRulesSet::~GaCaRulesSet() {
    this->clear();
    delete d;
}
 
size_t GaCaRulesSet::size() const {
    return d->set.size();
}
 
void GaCaRulesSet::add(GaCaRules *item) {
    d->add(item);
}
 
void GaCaRulesSet::clear() {
    Private::TSet::iterator iter;
    for(iter=d->set.begin(); iter!=d->set.end(); iter++) {
        Private::GaCaRulesCmpDecorator &i= 
            const_cast<Private::GaCaRulesCmpDecorator &>(*iter);
        i.dispose();
    }
    d->set.clear();
}
 
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SolutionsCountStop implementation
#ifndef BUILDING_DOX
struct SolutionsCountStop::Private {
    CaDesigner *solver;
    int               minCountOfSolutions;
};
#endif
 
SolutionsCountStop::SolutionsCountStop(CaDesigner *solver, int minCountOfSolutions):
    d(new Private)
{
    d->solver = solver;
    d->minCountOfSolutions = minCountOfSolutions;
}
 
SolutionsCountStop::~SolutionsCountStop() {
    delete d;
}
 
void SolutionsCountStop::notify() {
    const int nSolutions= d->solver->getSolutionsCount();
    if (nSolutions >= d->minCountOfSolutions)
        d->solver->stop();
}
 
 
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TimedStop implementation
#ifndef BUILDING_DOX
struct TimedStop::Private {
    AbstractProcessWatched *process;
    long msec;
};
#endif
 
TimedStop::TimedStop(AbstractProcessWatched *process, long msec):
    d(new Private)
{
    d->process = process;
    d->msec = msec;
}
 
TimedStop::~TimedStop() {
    delete d;
}
 
void TimedStop::notify() {
    long elapsed = d->process->getTimeElapsed();
    if (elapsed > d->msec) {
        std::cerr << Color(C_YELLOW) << "--- Run timed out"
            << Color(C_NO_COLOR) << std::endl;
        d->process->stop();
    }
}
 
namespace {
    void writeStats(CaDesigner *solver, std::ostream &str) {
        GAStatistics stats = solver->getStatistics();
        const int generation = stats.generation();
        const int stopAtGeneration = solver->stopAtGeneration();
        const float timeElapsed = solver->getTimeElapsed()/1000.0;
        const float timeRemaining = timeElapsed / generation
            * (stopAtGeneration - generation);
 
#if (1 < GA_POP_SIZE)
        str << " (avg:" << FixedFloat(3,1) << solver->avgFitness() * 100.0
            << ", min:" << FixedFloat(3,1) << solver->minFitness() * 100.0
            << ")";
#endif
        str << ", generation " << Color(C_LIGHT_GREEN)
            << std::setw(8) << generation << Color(C_NO_COLOR)
            << " of " << std::setw(8) << stopAtGeneration
            << ", time elapsed: " << Color(C_YELLOW)
            << FixedFloat(6,2) << timeElapsed << " s" << Color(C_NO_COLOR)
            << ", ttl = " << FixedFloat(6,2) << timeRemaining << " s";
    }
}
 
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// FitnessWatch implementation
#ifndef BUILDING_DOX
struct FitnessWatch::Private {
    CaDesigner          *solver;
    std::ostream        &stream;
    float               maxFitness;
 
    Private(std::ostream &streamTo): stream(streamTo) { }
};
#endif
 
FitnessWatch::FitnessWatch(CaDesigner *solver, std::ostream &streamTo):
    d(new Private(streamTo))
{
    d->solver = solver;
    d->maxFitness = 0.0;
}
 
FitnessWatch::~FitnessWatch() {
    delete d;
}
 
void FitnessWatch::notify() {
    CaDesigner *solver= d->solver;
    float maxFitness = solver->maxFitness();
    if (maxFitness <= d->maxFitness)
        // Fitness not changed
        return;
 
    GAStatistics stats = solver->getStatistics();
    if (0 == stats.generation())
        return;
 
    // Save maxFitness for next call
    d->maxFitness = maxFitness;
 
    d->stream << "---    satisfaction:" << Color(C_LIGHT_BLUE)
        << FixedFloat(3,1) << maxFitness*100.0 << "%"
        << Color(C_NO_COLOR);
    writeStats(solver, d->stream);
    d->stream << std::endl;
}
 
void FitnessWatch::reset() {
    d->maxFitness = 0.0;
}
 
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ResultsWatch implementation
#ifndef BUILDING_DOX
struct ResultsWatch::Private {
    CaDesigner   *solver;
    std::ostream        &stream;
    int                 nResults;
 
    Private(std::ostream &streamTo): stream(streamTo) { }
};
#endif
 
ResultsWatch::ResultsWatch(CaDesigner *solver, std::ostream &streamTo):
    d(new Private(streamTo))
{
    d->solver = solver;
    d->nResults = 0;
}
 
ResultsWatch::~ResultsWatch() {
    delete d;
}
 
void ResultsWatch::notify() {
    CaDesigner *solver= d->solver;
    const int nResults= solver->getSolutionsCount();
    if (nResults <= d->nResults)
        return;
    d->nResults = nResults;
 
    GAStatistics stats = solver->getStatistics();
    if (0 == stats.generation())
        return;
 
    d->stream << Color(C_LIGHT_BLUE) << "--- " << std::setw(6) << nResults
        << ". solution found" << Color(C_NO_COLOR);
    writeStats(solver, d->stream);
    d->stream << std::endl;
}
 
#ifndef BUILDING_DOX
struct ProgressWatch::Private {
    AbstractProcess *process;
    int             stepsTotal;
    int             last;
    std::ostream    &stream;
 
    Private(std::ostream &streamTo): stream(streamTo) { }
};
#endif
 
ProgressWatch::ProgressWatch(AbstractProcess *process, int stepsTotal, std::ostream &streamTo):
    d(new Private(streamTo))
{
    d->process = process;
    d->stepsTotal = stepsTotal;
    d->last = 0;
}
 
ProgressWatch::~ProgressWatch() {
    delete d;
}
 
void ProgressWatch::notify() {
 
    // Check percentage value
    const int currentStep= d->process->getStepsCount();
    const int percents=
        currentStep*100 /
        d->stepsTotal;
    if (percents == d->last)
        return;
    d->last = percents;
 
    // Write out message
    d->stream
        << Color(C_GREEN) << "--- Progress:"
        << std::setw(3) << percents << "%"
        << Color() << std::endl;
}