faber-tweann Roadmap
View SourceWhat this package intends to implement but does not yet.
README.md states what is. This file states what will be. A capability
moves from here into the README when it lands, accompanied by a test that
exercises it, and for performance claims by committed benchmark output.
Nothing here may be described as a feature in the README, the guides, EDoc comments, or the hex package description until it moves.
Ordering is not arbitrary. It follows the chapter dependency chain in Gene
Sher's Handbook of Neuroevolution Through Erlang, because that book is the
reference implementation for the DXNN2 architecture this package ports. See
faber-ecosystem/docs/CONFORMANCE.md for the module-by-module gap analysis and
faber-ecosystem/plans/PLAN_FABER_FOUNDATION.md for the full plan.
1. The scape and the fitness channel (Handbook Ch 7)
Status: not implemented. Blocks everything else.
morphology_*.erl in examples/ declare scape = {private, xor_sim},
{private, pb_sim}, {private, fx_sim}, {public, flatland} and
{public, snake_duel}. None of those five modules exist. There is no scape
module at all; Sher's scape concept survives only as an unused field on
#sensor{} and #actuator{}.
Consequence: no fitness value can reach the evolutionary loop. sensor.erl
returns zeros for unrecognised sensor names, actuator.erl passes its input
through, cortex.erl contains no reference to fitness, and
exoself.erl:412-416 computes fitness as lists:sum(Outputs). Nothing sends
the {exoself_terminated, Fitness} that population_monitor.erl:315-332 waits
for, so every agent times out after 5 seconds with fitness [0.0].
No end-to-end evolutionary run has ever completed in this codebase.
Required: scape.erl (gen/2, prep/1, name dispatch), xor_sim, scape
spawning in exoself (or adopt constructor.erl, which already wires
scape_pid correctly and has zero callers), the fitness message path
scape → actuator → cortex → exoself, and goal_reached propagation.
The exact message protocol is specified in
faber-ecosystem/docs/PROTOCOL.md.
2. The memetic tuning layer (Handbook Ch 10)
Status: configured but never invoked.
DXNN2's distinguishing property over NEAT is that it interleaves a stochastic hill-climber over synaptic weights with topological evolution. Three modules drive it, and none exist here:
| Module | Role | Called from, in DXNN2 |
|---|---|---|
tuning_selection | chooses which neurons to perturb | exoself:219, genome_mutator:218,262 |
tuning_duration | computes max_attempts per agent per generation | exoself:137 |
tot_topological_mutations | how many topological mutations to apply | genome_mutator:71 |
genotype.erl:212,214,217 assigns all three into the agent record and
exoself.erl:71-72,147-163 carries them in state. Nothing ever calls them.
exoself.erl:69 hardcodes max_attempts = 15 where DXNN2 computes it.
Until this lands, faber-tweann is a topology-and-weight evolver but not DXNN.
3. Mnesia genotype storage (Handbook Ch 8.4.1)
Status: not implemented. Storage is ETS, in-memory, lost on VM exit.
genotype:init_db/0 creates ETS tables. There is no mnesia: call anywhere in
src/. genotype.erl:166 notes that part of the API "exists for API
compatibility with the old Mnesia interface", and rebar.config records
"mnesia removed - now using ETS for genotype storage".
The README and CLAUDE.md claimed Mnesia persistence; those claims are being
corrected to describe ETS, and the capability is recorded here instead.
Intended: Mnesia with disc_copies, single node only. Not Mnesia
clustering across nodes, which would compete with the genome-over-mesh model
used downstream. This also resolves a live split-brain: faber-neuroevolution's
lc_chain.erl:398-450 reads genotypes expecting Mnesia while this package
stores them in ETS.
Cleanup that comes with it: remove the unused mnesia entry from
faber_tweann.app.src's applications list and the vestigial
{error, {mnesia_error, term()}} type in network_compiler.erl:43.
4. Oja's rule (Handbook Ch 15.2)
Status: native half exists, Erlang half does not.
native/faber_nn_nifs/src/lib.rs exports oja_update_batch/4, but there is no
plasticity_oja module implementing the plasticity behaviour, so it is
unreachable. Present implementations are plasticity_hebbian,
plasticity_modulated and plasticity_none.
5. Substrate encoding and HyperNEAT (Handbook Ch 16, Ch 17)
Status: not implemented. genotype.erl:271 raises
substrate_not_implemented.
This is the largest single item. Chapter 16 is 73 printed pages, roughly 42 of
listings, of which substrate.erl alone is a 19-page listing. Chapter 17 adds
18 pages.
New modules: substrate, substrate_cpp (coordinate pre-processor),
substrate_cep (connectivity expression producer).
Modified: genotype, exoself, genome_mutator, morphology, records.hrl.
A #substrate{} record was scaffolded but nothing implements it. It needs
id, agent_id, densities, linkform, plasticity, cpp_ids, cep_ids, plus a
substrate_id field on #agent{}.
Design note from the book, worth honouring: do not create separate
#substrate_cpp{} / #substrate_cep{} records. Sher explicitly rejects that
and instead adds a type field (neural | substrate_cpp | substrate_cep) to
the existing #sensor{} and #actuator{} records, so that every mutation
operator and linking function does not have to be duplicated.
Ch 16 can be built with plasticity = none; it does not hard-depend on Ch 15.
Ch 17 (abcn and iterative substrate plasticity) requires Ch 16.
When this lands, the "Substrate Networks" and "HyperNEAT via CPPNs" claims may return to the faber-ecosystem README. Not before.
6. Measured SIMD
Status: claimed in the past, never implemented, claims withdrawn in v2.0.0.
native/faber_nn_nifs/src/lib.rs contains zero SIMD: no std::arch, no
target_feature, no rayon, no par_iter. The former "SIMD Batch
Activations" heading described plain sequential map calls. The only
concurrency mechanism is rustler's DirtyCpu scheduler flag.
Intended: real vectorisation on the dense/layered path. The sparse
topological-order graph walk in CompiledNetwork::evaluate will not vectorise
usefully and should be left scalar.
Prerequisite: a benchmark harness whose output is committed.
test/benchmark/bench_nif_vs_erlang.erl exists but no recorded numbers do.
No speedup figure may be published without committed output naming its
execution path via tweann_nif:impl/0.
7. Instrumentation (Handbook Ch 12, Ch 13)
Status: not implemented.
#trace{} and #stat{} records exist in records.hrl but no code emits them.
benchmarker, polis and trainer do not exist.
Without benchmarker and the goal_reached signal from item 1, average
evaluations-to-solve cannot be measured, and therefore no comparison against
the published pole-balancing literature is possible.
Not planned
- Mnesia clustering across nodes. Single-node
disc_copiesonly. Genome distribution belongs to the mesh layer, not to the storage engine. - Prebuilt NIF artifacts. NIFs are built from source. Shipping prebuilt shared objects couples the package to a specific glibc.
- A silent NIF fallback. Removed in v2.0.0. Implementation is selected explicitly and a missing library raises. Two implementations silently disagreed for months because the native path was never exercised.