Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.beam
112 changes: 111 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,113 @@
# GeneticLearningErlang

Supervised learning algorithm written in erlang to learn the phrase "I'm Learning"
A genetic algorithm implementation in Erlang that evolves random strings toward a target phrase using selection, crossover, and mutation. Now with multiple modes that showcase Erlang's concurrency strengths.

## Quick Start

```bash
# Compile all modules
erlc ga.erl race.erl island.erl creatures.erl

# Run the basic GA (evolves toward "I'm learning!")
erl -run ga start -run init stop -noshell
```

## Modules

### `ga` - Core Genetic Algorithm

The enhanced core module with colorized output, stats tracking, and multiple selection strategies.

```erlang
%% Default: evolve "I'm learning!" with tournament selection
ga:start().

%% Custom target phrase
ga:start("Hello World!").

%% Custom target + population size
ga:start("Erlang rocks!", 2000).

%% Full control: target, population size, selection strategy
%% Strategies: tournament | roulette | rank
ga:start("Go Erlang!", 3000, roulette).
```

Each generation displays:
- **Colorized best attempt** (green = matching chars, red = wrong)
- **Progress bar** showing fitness percentage
- **Stats**: best fitness, average fitness, population diversity

### `race` - Parallel Population Racing

Spawns three populations side by side, each using a different selection strategy (tournament, roulette, rank), and races them to see which converges first.

```erlang
%% Race with default target
race:start().

%% Race with custom target
race:start("May the best win!").

%% Custom target + population size per racer
race:start("Speed test", 500).
```

Results are displayed on a podium showing placement, generation count, and elapsed time.

### `island` - Island Model with Migration

Runs multiple isolated populations as separate Erlang processes. Every few generations, top individuals migrate between islands, spreading good genes. A classic parallel GA technique that's a natural fit for Erlang's process model.

```erlang
%% Default: 4 islands, 1000 individuals each
island:start().

%% Custom target
island:start("Island life!").

%% Custom target, number of islands, population per island
island:start("Archipelago", 6, 500).
```

Each island uses a different selection strategy. Migration happens every 10 generations, sending the 5 best individuals to a random neighbor.

### `creatures` - ASCII Art Evolution

Evolves random characters into ASCII art shapes. Watch chaos slowly assemble into a recognizable creature.

```erlang
%% Evolve a cat (default)
creatures:start().

%% Choose a creature: cat | house | heart | rocket | smiley
creatures:start(heart).
creatures:start(rocket).

%% List all available creatures with previews
creatures:list().
```

Creatures are displayed as 2D grids with matching characters highlighted in green.

## How It Works

1. **Population**: Generate N random strings the same length as the target
2. **Fitness**: Sum of absolute ASCII differences between each character and the target (lower = better, 0 = perfect)
3. **Selection**: Pick the fittest individuals to be parents
- **Tournament**: Pair up individuals randomly, winner of each pair survives
- **Roulette**: Probability of selection proportional to fitness
- **Rank**: Sort by fitness, top half survives
4. **Crossover**: Split two parents at the midpoint and swap halves to create children
5. **Mutation**: Randomly shift a character's ASCII value by 1-5 (20% chance per individual)
6. **Repeat** until the target is found

## Configuration

Default parameters in `ga.erl`:

| Parameter | Default | Description |
|-----------|---------|-------------|
| `POPULATION_SIZE` | 4000 | Individuals per generation |
| `MUTATION_RATE` | 2 | Numerator for mutation probability |
| `MUTATION_CAP` | 10 | Denominator for mutation probability (rate = 2/10 = 20%) |
179 changes: 179 additions & 0 deletions creatures.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
-module(creatures).
-compile(export_all).

%%% ============================================================
%%% ASCII Art Evolution
%%% ============================================================
%%% Evolve random characters into ASCII art creatures!
%%% Watch as chaos slowly assembles into a recognizable shape.
%%% Available creatures: cat, house, heart, rocket, smiley
%%% ============================================================

%% ANSI colors
-define(GREEN, "\033[32m").
-define(RED, "\033[31m").
-define(CYAN, "\033[36m").
-define(YELLOW, "\033[33m").
-define(BOLD, "\033[1m").
-define(DIM, "\033[2m").
-define(RESET, "\033[0m").

-define(POP_SIZE, 500).

%% Evolve a cat by default
start() ->
start(cat).

%% Evolve a named creature: cat | house | heart | rocket | smiley
start(CreatureName) ->
random:seed(now()),
Rows = getCreature(CreatureName),
Width = length(hd(Rows)),
NumRows = length(Rows),
FlatTarget = lists:flatten(Rows),

io:format("~n~s~s=== ASCII Art Evolution ===~s~n", [?BOLD, ?CYAN, ?RESET]),
io:format(" Creature: ~s~p~s~n", [?YELLOW, CreatureName, ?RESET]),
io:format(" Grid: ~px~p (~p chars)~n~n", [Width, NumRows, length(FlatTarget)]),

io:format(" ~sTarget:~s~n", [?BOLD, ?RESET]),
printGrid(Rows, " "),
io:format("~n"),

Pop = [ga:rndPrintableString(length(FlatTarget)) || _ <- lists:seq(1, ?POP_SIZE)],
StartTime = erlang:monotonic_time(millisecond),
evolve(Pop, 0, FlatTarget, Width, StartTime).

%%% ============================================================
%%% Creature definitions (all rows same width, padded with spaces)
%%% ============================================================

getCreature(cat) ->
[" /\\_/\\ ",
"( o.o )",
" > ^ < "];

getCreature(house) ->
[" /\\ ",
" / \\ ",
"/ \\",
"| |",
"| [] |",
"|____|"];

getCreature(heart) ->
[" ** ** ",
"********",
"********",
" ****** ",
" **** ",
" ** "];

getCreature(rocket) ->
[" /\\ ",
" / \\ ",
" | ** | ",
" | ** | ",
" | ** | ",
" /| |\\ ",
"/_|__|_\\"];

getCreature(smiley) ->
[" ------ ",
"| o o |",
"| /\\ |",
"| \\__/ |",
" ------ "].

%% List available creatures
list() ->
io:format("~nAvailable creatures:~n"),
lists:foreach(fun(Name) ->
Rows = getCreature(Name),
io:format("~n ~s~p~s:~n", [?YELLOW, Name, ?RESET]),
printGrid(Rows, " ")
end, [cat, house, heart, rocket, smiley]),
io:format("~n").

%%% ============================================================
%%% Evolution loop
%%% ============================================================

evolve(Pop, Gen, Target, Width, StartTime) ->
ParentPop = ga:tournamentSelection(Pop, Target),
NewPop = ga:breed(ParentPop),
MNewPop = ga:performMutations(NewPop),
Best = ga:getBest(MNewPop, Target),
BestFit = ga:fitness(Best, Target),

%% Show creature every 10 generations
case Gen rem 10 of
0 -> printProgress(Best, Target, Width, Gen, BestFit);
_ -> ok
end,

case Best == Target of
true ->
Elapsed = erlang:monotonic_time(millisecond) - StartTime,
io:format("~n ~s~s*** CREATURE EVOLVED! ***~s~n~n", [?BOLD, ?GREEN, ?RESET]),
printSideBySide(Target, Best, Width),
io:format("~n Generations: ~p Time: ~p ms~n~n", [Gen, Elapsed]),
{ok, Best, Gen};
false ->
evolve(MNewPop, Gen + 1, Target, Width, StartTime)
end.

%%% ============================================================
%%% Display functions
%%% ============================================================

printProgress(Current, Target, Width, Gen, Fit) ->
io:format(" ~s--- Gen ~p (fit: ~p) ---~s~n", [?CYAN, Gen, Fit, ?RESET]),
CurrentRows = splitRows(Current, Width),
TargetRows = splitRows(Target, Width),
lists:foreach(fun({CRow, TRow}) ->
io:format(" "),
printColorizedRow(CRow, TRow),
io:format("~n")
end, lists:zip(CurrentRows, TargetRows)),
io:format("~n").

printSideBySide(Target, Current, Width) ->
TargetRows = splitRows(Target, Width),
CurrentRows = splitRows(Current, Width),
io:format(" ~sTarget:~s~s~sEvolved:~s~n",
[?BOLD, ?RESET, lists:duplicate(Width, $\s), ?BOLD, ?RESET]),
lists:foreach(fun({TRow, CRow}) ->
io:format(" ~s~s~s", [?GREEN, TRow, ?RESET]),
io:format(" "),
io:format("~s~s~s", [?GREEN, CRow, ?RESET]),
io:format("~n")
end, lists:zip(TargetRows, CurrentRows)).

printColorizedRow([], []) -> ok;
printColorizedRow([C|Cs], [T|Ts]) ->
case C == T of
true -> io:format("~s~c~s", [?GREEN, C, ?RESET]);
false -> io:format("~s~c~s", [?RED, C, ?RESET])
end,
printColorizedRow(Cs, Ts);
printColorizedRow(_, _) -> ok.

printGrid(Rows, Indent) ->
lists:foreach(fun(Row) ->
io:format("~s~s~s~s~s~n", [Indent, ?GREEN, Row, ?RESET, ""])
end, Rows).

%%% ============================================================
%%% Utilities
%%% ============================================================

splitRows(Flat, Width) ->
splitRows(Flat, Width, []).

splitRows([], _, Acc) ->
lists:reverse(Acc);
splitRows(Flat, Width, Acc) ->
Len = min(Width, length(Flat)),
{Row, Rest} = lists:split(Len, Flat),
splitRows(Rest, Width, [Row | Acc]).
Loading