Skip to content

Commit 23f7924

Browse files
author
Chris Sullivan
committed
Upgrades and bug fixes:
- Replaced search for unused connection in MutateConnection with ReachabilityChecker, which can now return unused random connections. * This removed the need for MutateConnections to take in a NeuralNet reference as an argument, and so it was removed, and also removed from the Organism struct, since this was the primary motivation for placing this pointer in the organism. - Added Population copy constructor, as well as move and copy assignment operators. - ReachabilityChecker now required the number of inputs be specified on construction, and so a member was added to the Genome to keep track of the number of sensor nodes (input + bias) added into a genome. - In both AddNode & AddConnection a static member was used for saving the last innovation number, this was meant to be global only within a genome, thus it was made a class member. This is also the case for node indexes in AddNode for producing innovation numbers for newly created nodes. This could probably be redesigned a bit. - In Genome::MateWith, the very first action is now to add the sensor nodes of one of the parents into the child since they should be static and unchanging in the evolution. This then prevents any issues with reordering of nodes where the input nodes would possibly not be the first nodes in the node_genes vector. This is particularly important for the ReachabilityChecker which assumes the first num_inputs nodes are inputs. - Fixed a bug in NeuralNet::would_make_loop which would not correctly mark a connection from a node to itself as recurrent. Now this check is made up front (if i==j return true). - There was a bug with the culling_ratio in the species implementation where if the culling_ratio was 0.5 and there were only two species crossover could not proceed as there are two few specimen. Now if the number of specimen in a species is ==2 and the culling ratio is less than or equal to 0.5, the more fit of the two specimen is mutated and added to the output genome list. - Added neat_xor test which after the above changes runs to completion, however is not finding a solution consistently or quickly enough.
1 parent 06d75a0 commit 23f7924

11 files changed

+364
-103
lines changed

libNeat/include/Genome.hh

+10-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class ReachabilityChecker;
2929
class Genome : public uses_random_numbers,
3030
public requires<Probabilities> {
3131
public:
32+
Genome();
3233
operator NeuralNet() const;
3334
Genome operator=(const Genome&);
3435
//Genome& operator=(Genome);
@@ -39,8 +40,7 @@ public:
3940
Genome MateWith(const Genome& father);
4041
Genome MateWith(Genome* father);
4142
void Mutate();
42-
void Mutate(const NeuralNet&);
43-
void MutateConnection(const NeuralNet&);
43+
void MutateConnection();
4444
void MutateNode();
4545
void MutateWeights();
4646
void MutateReEnableGene();
@@ -56,7 +56,15 @@ private:
5656

5757

5858
private:
59+
size_t num_inputs;
5960
std::vector<NodeGene> node_genes;
6061
std::unordered_map<unsigned long,unsigned int> node_lookup;
6162
insertion_ordered_map<unsigned long, ConnectionGene> connection_genes;
63+
64+
// innovation record keeping
65+
unsigned long last_conn_innov;
66+
unsigned long last_node_innov;
67+
unsigned long idxinput;
68+
unsigned long idxoutput;
69+
unsigned long idxhidden;
6270
};

libNeat/include/Population.hh

+5-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public:
1616
Population(std::vector<Genome> population,
1717
std::shared_ptr<RNG>,std::shared_ptr<Probabilities>);
1818

19+
Population(const Population&);
20+
21+
Population& operator=(Population&&);
1922
/// Evaluate the fitness function for each neural net.
2023
template<class Callable>
2124
void Evaluate(Callable&& fitness) {
@@ -25,7 +28,6 @@ public:
2528
Organism org = {};
2629
org.fitness = fitness(net);
2730
org.genome = &population[n++];
28-
org.net = &net;
2931
organisms.push_back(org);
3032
}
3133
}
@@ -43,6 +45,8 @@ public:
4345
*/
4446
Population Reproduce();
4547

48+
Population operator=(const Population& rhs);
49+
4650
private:
4751
struct Organism {
4852
// A proxy class for class Genome, containing its
@@ -52,7 +56,6 @@ private:
5256
float fitness;
5357
float adj_fitness;
5458
Genome* genome;
55-
NeuralNet* net;
5659
};
5760

5861
void build_networks();

libNeat/include/ReachabilityChecker.hh

+76-8
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,90 @@
11
#pragma once
2-
#include <vector>
2+
33
#include <cstring>
4+
#include <utility>
5+
#include <vector>
6+
7+
class RNG;
48

59
class ReachabilityChecker {
610
public:
7-
ReachabilityChecker(size_t num_nodes);
11+
/// Constructs the reachability checker
12+
/*
13+
num_nodes is the total number of nodes.
14+
num_inputs is the number of input nodes.
15+
16+
Here, an input node is defined as any node that cannot be the
17+
destination of a connection.
18+
The input nodes are at indices [0, num_inputs).
19+
*/
20+
ReachabilityChecker(size_t num_nodes, size_t num_inputs = 0);
21+
22+
/// Adds a connection
23+
/*
24+
origin is the index of the origin-node
25+
destination is the index of the destination-node.
826
27+
Assumes that destination >= num_inputs.
28+
29+
The connection added is a normal connection,
30+
unless adding such a connection would cause the normal connections to contain a loop.
31+
In that case, the connection added is recurrent.
32+
*/
933
void AddConnection(size_t origin, size_t destination);
1034

35+
/// Checks whether a given connection has been defined
1136
bool HasConnection(size_t origin, size_t destination) const {
1237
return at(origin,destination).has_any_connection;
1338
}
39+
40+
/// Checks whether a given connection has been defined, and is normal
1441
bool HasNormalConnection(size_t origin, size_t destination) const {
1542
return at(origin,destination).has_normal_connection;
1643
}
44+
45+
/// Checks whether a given connection has been defined, and is recurrent
1746
bool HasRecurrentConnection(size_t origin, size_t destination) const {
1847
return at(origin,destination).has_recurrent_connection;
1948
}
2049

50+
/// Return true if a path from origin to destination exists using only normal connections.
2151
bool IsReachableNormal(size_t origin, size_t destination) const {
2252
return at(origin,destination).reachable_normal;
2353
}
54+
55+
/// Return true if a path from origin to destination exists using any connections.
2456
bool IsReachableEither(size_t origin, size_t destination) const {
2557
return at(origin,destination).reachable_either;
2658
}
2759

28-
// bool IsFullyConnected() const {
29-
// }
30-
// std::pair<size_t,size_t> AddRandomRecurrent() {
31-
// }
32-
// std::pair<size_t,size_t> AddRandomNormal() {
33-
// }
60+
/// Returns true if no additional connections can be added.
61+
bool IsFullyConnected() const {
62+
return (num_possible_normal_connections == 0 &&
63+
num_possible_recurrent_connections == 0);
64+
}
65+
66+
/// Returns the number of different normal connections that could be added.
67+
size_t NumPossibleNormalConnections() const {
68+
return num_possible_normal_connections;
69+
}
70+
71+
/// Returns the number of different recurrent connections that could be added.
72+
size_t NumPossibleRecurrentConnections() const {
73+
return num_possible_recurrent_connections;
74+
}
75+
76+
/// Returns a randomly selected normal connection to add.
77+
/**
78+
If no such connection exists, returns (-1,-1).
79+
*/
80+
std::pair<int,int> RandomNormalConnection(RNG& rng);
81+
82+
/// Returns a randomly selected recurrent connection to add.
83+
/**
84+
If no such connection exists, returns (-1,-1).
85+
*/
86+
std::pair<int,int> RandomRecurrentConnection(RNG& rng);
87+
3488

3589

3690

@@ -49,6 +103,10 @@ private:
49103
};
50104

51105
size_t num_nodes;
106+
size_t num_inputs;
107+
size_t num_possible_normal_connections;
108+
size_t num_possible_recurrent_connections;
109+
52110
std::vector<MatrixElement> mat;
53111

54112
MatrixElement& at(size_t i, size_t j) {
@@ -60,4 +118,14 @@ private:
60118

61119
void fill_normal_reachable(size_t origin, size_t destination);
62120
void fill_either_reachable(size_t origin, size_t destination);
121+
122+
bool could_add_normal(size_t origin, size_t destination) {
123+
return (!at(origin,destination).has_any_connection &&
124+
!at(destination, origin).reachable_normal);
125+
}
126+
127+
bool could_add_recurrent(size_t origin, size_t destination) {
128+
return (!at(origin,destination).has_any_connection &&
129+
at(destination, origin).reachable_normal);
130+
}
63131
};

libNeat/include/Requirements.hh

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public:
99
std::shared_ptr<T> required() const { return required_; }
1010
void required(const std::shared_ptr<T>& req) { required_ = req; }
1111

12-
private:
12+
protected:
1313
std::shared_ptr<T> required_;
1414
};
1515

0 commit comments

Comments
 (0)