2

I am attempting to make an AI similar to NEAT using C++. However, I have run into a problem with getting the population to work correctly. When I use an array of custom objects, some of the data stored in each of the objects' properties is being corrupted. The data inside each of the connections in connections is set to 0xDDDDDDDD after the net is assigned. When I test the given properties inside the constructor it seems fine but when it is assigned within the population array (in this case t) all of the data is set to 0xDDDDDDDD similarly to if it were deleted.

    // neat network.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#include <cmath>

class node
{
public:
    int id;
    double value;
    node()
    {
        id = 0;
        value = 0;
    }
    node(int n)
    {
        id = n;
        value = 0;
    }
    void apply(double v)
    {
        value = v;
    }
    node copy()
    {
        return node(id);
    }
};

class connection
{
public:
    double weight;
    int in;
    int out;
    connection(int i, int o, double w=0)
    {
        in = i;
        out = o;
        weight = w;
        int r = rand();
        weight = ((double)(r % 2000) - 1000.0) / 1000.0;
        srand(r);
    }
    connection()
    {
        in = 0;
        out = 0;
        weight = 0;
    }
    void mutate()
    {
        weight += (double)((rand() % 50 - 2) / 1000);
    }
    connection copy()
    {
        connection ret = connection(in, out);
        ret.weight = weight;
        return ret;
    }
};

double sigmoid(double n)
{
    return 1 / (exp(-n) + 1);
}

class net
{
public:
    double* position;
    int quant;
    node* nodes;
    int* inpernode;
    connection* connections;
    int connlen;
    int inn;
    int ut;
    net(int ins, int outs, int cn=0)
    {
        inn = ins;
        ut = outs;
        quant = ins+outs;
        nodes = new node[quant];
        position = new double[quant];
        connections = new connection[ins*outs];
        inpernode = new int[quant];
        for (int i = 0; i < quant; i++)
        {
            nodes[i] = node(i);
            if (i < ins)
            {
                position[i] = 0;
            }
            else
            {
                position[i] = 1;
            }
            inpernode[i] = 0;
        }
        int c = 0;
        connlen = ins * outs;
        for (int i = 0; i < ins; i++)
        {
            for (int j = ins; j < quant; j++)
            {
                connections[c] = connection(i, j);
                //std::cout << connections[c].weight << " ";
                inpernode[j]++;
                c++;
            }
        }
        //std::cout << connections[0].in << std::endl;
    }
    net()
    {
        connections = new connection();
        connlen = 0;
        inn = 0;
        inpernode = new int();
        quant = 0;
        nodes = new node();
        position = new double();
        ut = 0;
    }
    ~net()
    {
        delete[] position;
        delete[] nodes;
        delete[] connections;
        delete[] inpernode;
    }
    double* apply(double* inputs)
    {
        bool* finished = new bool[quant];
        //std::cout << connections[0].in;
        bool* cfinished = new bool[connlen];
        int* ndone = new int[quant];
        bool alldone=false;
        for (int i = 0; i < connlen; i++)
        {
            cfinished[i] = false;
        }
        for (int i = 0; i < quant; i++)
        {
            finished[i] = i<inn;
            ndone[i] = 0;
            nodes[i].value = 0;
            if (i < inn) {
                nodes[i].value = inputs[i];
            }
        }
        while (!alldone)
        {
            alldone = true;
            for (int i = 0; i < connlen; i++)
            {
                //std::cout << quant;
                if (finished[connections[i].in] && !cfinished[i])
                {
                    alldone = false;
                    nodes[connections[i].out].value += connections[i].weight * sigmoid(nodes[connections[i].in].value);
                    cfinished[i] = true;
                    ndone[connections[i].out]++;
                    if (ndone[connections[i].out] == inpernode[connections[i].out])
                    {
                        finished[connections[i].out] = true;
                    }
                }
            }
        }
        double* outs = new double[ut];
        for (int i = inn; i < inn + ut; i++)
        {
            outs[i - inn] = sigmoid(nodes[i].value);
        }
        return outs;
    }
    net copy()
    {
        net ret = net(inn, ut);
        ret.quant = quant;
        ret.connlen = connlen;
        for (int i = 0; i < quant; i++)
        {
            ret.position[i] = position[i];
            ret.nodes[i] = nodes[i].copy();
            ret.inpernode[i] = inpernode[i];
        }
        for (int i = 0; i < connlen; i++)
        {
            ret.connections[i] = connections[i].copy();
        }
        return ret;
    }
    net mutate()
    {
        net ret = copy();
        for (int i = 0; i < quant; i++)
        {
            if (rand() % 20 == 19)
            {
                connections[i].mutate();
            }
            if (rand() % 333 == 332)
            {
                nodes[quant] = node(quant);
                int temp = connections[i].out;
                connections[i].out = quant;
                connections[connlen] = connection(quant, temp);
                quant++;
                connlen++;
            }
        }
        if (rand() % 33 == 32)
        {
            bool done = false;
            int tries = 200;
            while (!done)
            {
                tries--;
                if (tries < 0)
                    done = true;
                int inc = rand() % quant;
                if (position[inc] == 1)
                    continue;
                int utc = rand() % quant;
                if (position[inc] > position[utc])
                    continue;
                bool found = false;
                for (int i = 0; i < connlen; i++)
                {
                    if (connections[i].in == inc && connections[i].out == utc)
                        found = true;
                }
                if (!found)
                {
                    connections[connlen] = connection(inc, utc);
                    connlen++;
                    done = true;
                }
            }
        }
        return ret;
    }
};

int main()
{
    srand(2002);
    /*net test = net(2, 1);
    net test2 = net(2, 1);
    std::cout << test.connections[0].weight << " " << test.connections[1].weight << std::endl;
    std::cout << test2.connections[0].weight << " " << test2.connections[1].weight << std::endl;
    double ins[] = { 0,0 };
    double* cp = test.apply(ins);
    std::cout << cp[0]<<" ";
    cp = test2.apply(ins);
    std::cout << cp[0];*/
    net t[1];
    t[0] = net(2, 1);
    std::cout << t[0].inn << " " << t[0].ut << std::endl;
    std::cout << t[0].connections[0].in << " " << t[0].connections[0].weight << std::endl;
    /*net pop[100];
    for (int i = 0; i < 100; i++)
    {
        pop[i] = net(2, 1);
        std::cout << pop[i].connections[0].in << " " << pop[i].connections[1].in << " " << std::endl;
    }
    int* scores = new int[100];
    for (int gen = 0; gen < 100; gen++)
    {
        for (int i = 0; i < 100; i++)
        {
            scores[i] = 0;
            double inp[] = { 0, 0 };
            double* t = pop[i].apply(inp);
            if (t[0] < .1)
                scores[i]++;
            inp[1] = 1;
            t = pop[i].apply(inp);
            if (t[0] > .9)
                scores[i]++;
            inp[0] = 1;
            inp[1] = 0;
            t = pop[i].apply(inp);
            if (t[0] > .9)
                scores[i]++;
            inp[1] = 1;
            t = pop[i].apply(inp);
            if (t[0] < .1)
                scores[i]++;
            if (scores[i] == 4)
            {
                std::cout << "Solved" << gen;
                return 0;
            }
            scores[i] -= pop[i].connlen + pop[i].quant;
        }
        int sum = 0;
        for (int i = 0; i < 100; i++)
            sum += scores[i];
        double avg = (double)(sum / 100);
        net newpop[100];
        int nplen = 0;
        for (int i = 0; i < 100; i++)
        {
            if (scores[i] > avg)
            {
                newpop[nplen++] = pop[i];
            }
        }
        for (int i = 0; i < nplen; i++)
        {
            pop[i] = newpop[i];
        }
        for (int i = nplen; i < 100; i++)
        {
            pop[i] = pop[i % nplen].mutate();
        }
    }*/
    return 0;
}

// Run program: Ctrl + F5 or Debug > Start Without Debugging menu
// Debug program: F5 or Debug > Start Debugging menu

// Tips for Getting Started: 
//   1. Use the Solution Explorer window to add/manage files
//   2. Use the Team Explorer window to connect to source control
//   3. Use the Output window to see build output and other messages
//   4. Use the Error List window to view errors
//   5. Go to Project > Add New Item to create new code files, or Project > Add Existing Item to add existing code files to the project
//   6. In the future, to open this project again, go to File > Open > Project and select the .sln file
manlio
  • 18,345
  • 14
  • 76
  • 126
  • `0xDD` is used by Microsoft's runtime to mark freed memory – Alan Birtles Nov 24 '19 at 21:44
  • I am aware of that but I am not sure why it is freeing up that memory. – opfromthestart Nov 24 '19 at 21:45
  • Possible duplicate of [What is The Rule of Three?](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) wrt to `net` with this line `t[0] = net(2, 1);` Also https://stackoverflow.com/questions/4782757/rule-of-three-becomes-rule-of-five-with-c11 – Richard Critten Nov 24 '19 at 21:47
  • The code works when I remove the destructor. How am I supposed to delete nodes and connections. – opfromthestart Nov 24 '19 at 23:48
  • Your class `net` manages dynamically allocated memory. Search for information on "rule of three" or (more modern) "rule of five" in context of C++ - one of those rules needs to be followed for class `net`. – Peter Nov 25 '19 at 00:11
  • It will always give me an error when I attempt to use delete[] in my program, and will have other memory errors (I assume because I run out of memory too fast) if I cannot delete them. – opfromthestart Nov 26 '19 at 01:23
  • Don’t use raw pointers unless you’re implementing a new kind of memory resource (which you aren’t). Don’t call `srand` repeatedly. – Davis Herring Nov 26 '19 at 03:36

1 Answers1

1

Notes in random order

* Weight is ignored / multiple calls to srand

connection(int i, int o, double w=0)
{
  in = i;
  out = o;
  weight = w;

  int r = rand();
  weight = ((double)(r % 2000) - 1000.0) / 1000.0;
  srand(r);
}

The user specified weight is always overwritten with a random value.

You should probably write two different constructors:

connection(int i, int o)
  : weight((static_cast<double>(rand() % 2000) - 1000.0) / 1000.0),
    in(i), out(o)  
{
}

connection(int i, int o, double w) : weight(w), in(i), out(o) {}

Consider that multiple calls to srand must be avoided.

Also srand doesn't work the way you seem to think. The initial call to rand returns a well defined value that is then used as seed: this seed is not random at all so there isn't any randomness in chain of calls to srand / rand.

Moreover intermixed calls to srand could affect quality of rand (which already in itself isn't great).

* mutate always returns 0

void mutate()
{
  weight += (double)((rand() % 50 - 2) / 1000);
}

it should probably be:

void mutate()
{
  weight += (double)((rand() % 50 - 2)) / 1000;
}

* Deallocation

~net()
{
  delete[] position;
  delete[] nodes;
  delete[] connections;
  delete[] inpernode;
}

This assumes that position, nodes, connections, ipernode are array of objects but they could be a single object, e.g.:

net()
{
  connections = new connection();
  inpernode = new int();
  nodes = new node();
  position = new double();

  // ...
}

(see delete vs delete[] operators in C++).

Reported issue

net t[1];

constructs a single net object calling net().

t[0] = net(2, 1);

has (at least) four effects:

  1. creates a new net object (net(2, 1));
  2. deletes the existing t[0] object;
  3. copies net(2, 1) inside t[0];
  4. deletes net(2, 1).

Point 2 is problematic (see above: Deallocation).

After point 3 you have:

t[0].connections ----points to----------> [connection1, connection2]
                                                     ^
                                                     |
net(2, 1).connections ----points to------------------+

After point 4:

t[0].connections ----points to----------> [freed memory area]

`net(2, 1)` destroyed

and the same holds for position and inpernode.

Now

std::cout << t[0].connections[0].in;

accesses a freed memory area (the 0xDDDDDDDD value...).

Deletion of t[0] (at the end of main) involves the deallocation of an already freed memory block (with an error like double free detected in tcache 2).

TL;DR

  • At least add a copy assignment operator and a copy constructor (rule of three) to the net class. Also change net() to something like:

    net()
    {
      connections = nullptr;
      connlen = 0;
      inn = 0;
      inpernode = nullptr;
      quant = 0;
      nodes = nullptr;
      position = nullptr;
      ut = 0;
    }
    
  • All these headaches occur because of manual memory management. You could easily rewrite the code using std::vectors and avoid writing any custom copy/move constructors, assignment operators or destructors (rule of zero)... and this is the way to go.

PS I've not checked the net::apply, net::mutate, net::copy member functions.

manlio
  • 18,345
  • 14
  • 76
  • 126