feat: scaffold FCES-native C++ project with libtorch integration
- CMakeLists.txt with libtorch, GoogleTest, GoogleBenchmark, OpenMP, pybind11 - Header files: config, controller, population, fitness, evolution, spectral, oscillation, telemetry, optimizer - Source implementations: controller (full micro-MLP forward pass, mutation, crossover), fitness (Welford's algorithm), oscillation (DFT), spectral (SVD rank), optimizer (sign-SGD stub) - Tests: controller, population, fitness, optimizer (Google Test) - Benchmarks: evolve throughput, optimizer step (Google Benchmark) - Examples: simple optimization, PyTorch/libtorch integration - Python extension: pybind11 bindings with setup.py - README with architecture diagram and build instructions
This commit is contained in:
174
src/controller.cpp
Normal file
174
src/controller.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "fces/controller.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
|
||||
namespace fces {
|
||||
|
||||
// Static members
|
||||
std::atomic<uint64_t> FuzzyController::next_id_{0};
|
||||
thread_local std::mt19937 FuzzyController::rng_{std::random_device{}()};
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Genome
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
void Genome::randomize(std::mt19937& rng) {
|
||||
std::normal_distribution<float> dist(0.0f, 0.5f);
|
||||
for (auto& w : weights) {
|
||||
w = dist(rng);
|
||||
}
|
||||
gene_success.fill(0.0f);
|
||||
}
|
||||
|
||||
Genome Genome::clone() const {
|
||||
return *this; // Copy all fields
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// FuzzyController
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
FuzzyController::FuzzyController()
|
||||
: id(next_id_++), origin("random") {
|
||||
genome.randomize(rng_);
|
||||
// Bias output toward acceleration (V2.1 insight)
|
||||
// Set output biases (last GENOME_OUTPUT_DIM elements) to +2.0
|
||||
constexpr int bias_start = GENOME_SIZE - GENOME_OUTPUT_DIM;
|
||||
for (int i = bias_start; i < GENOME_SIZE; ++i) {
|
||||
genome.weights[i] = 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
FuzzyController::FuzzyController(Genome genome)
|
||||
: id(next_id_++), genome(std::move(genome)), origin("constructed") {}
|
||||
|
||||
torch::Tensor FuzzyController::decide_update(
|
||||
const std::vector<std::vector<float>>& layer_stats,
|
||||
float loss_trend,
|
||||
float step_pct,
|
||||
float rollback_rate,
|
||||
float grad_stability,
|
||||
float spectral_alpha,
|
||||
float stagnation_intensity,
|
||||
float kzm_damping,
|
||||
float projected_drift
|
||||
) {
|
||||
const int num_groups = static_cast<int>(layer_stats.size());
|
||||
auto actions = torch::zeros({num_groups, GENOME_OUTPUT_DIM});
|
||||
|
||||
// Extract weight views for the micro-MLP
|
||||
const float* w = genome.weights.data();
|
||||
|
||||
// Layer 1: input -> hidden
|
||||
const float* W1 = w; // [GENOME_INPUT_DIM x GENOME_HIDDEN_DIM]
|
||||
const float* b1 = w + (GENOME_INPUT_DIM * GENOME_HIDDEN_DIM); // [GENOME_HIDDEN_DIM]
|
||||
// Layer 2: hidden -> output
|
||||
const float* W2 = b1 + GENOME_HIDDEN_DIM; // [GENOME_HIDDEN_DIM x GENOME_OUTPUT_DIM]
|
||||
const float* b2 = W2 + (GENOME_HIDDEN_DIM * GENOME_OUTPUT_DIM); // [GENOME_OUTPUT_DIM]
|
||||
|
||||
for (int g = 0; g < num_groups; ++g) {
|
||||
// Build input vector
|
||||
std::array<float, GENOME_INPUT_DIM> input{};
|
||||
if (!layer_stats[g].empty()) {
|
||||
// Copy available stats, pad with context
|
||||
const int n = std::min(static_cast<int>(layer_stats[g].size()), GENOME_INPUT_DIM - 4);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
input[i] = layer_stats[g][i];
|
||||
}
|
||||
}
|
||||
// Append global context
|
||||
input[GENOME_INPUT_DIM - 4] = loss_trend;
|
||||
input[GENOME_INPUT_DIM - 3] = step_pct;
|
||||
input[GENOME_INPUT_DIM - 2] = grad_stability;
|
||||
input[GENOME_INPUT_DIM - 1] = stagnation_intensity;
|
||||
|
||||
// Forward pass: hidden = tanh(W1 * input + b1)
|
||||
std::array<float, GENOME_HIDDEN_DIM> hidden{};
|
||||
for (int h = 0; h < GENOME_HIDDEN_DIM; ++h) {
|
||||
float sum = b1[h];
|
||||
for (int i = 0; i < GENOME_INPUT_DIM; ++i) {
|
||||
sum += W1[h * GENOME_INPUT_DIM + i] * input[i];
|
||||
}
|
||||
hidden[h] = std::tanh(sum);
|
||||
}
|
||||
|
||||
// Output = W2 * hidden + b2
|
||||
for (int o = 0; o < GENOME_OUTPUT_DIM; ++o) {
|
||||
float sum = b2[o];
|
||||
for (int h = 0; h < GENOME_HIDDEN_DIM; ++h) {
|
||||
sum += W2[o * GENOME_HIDDEN_DIM + h] * hidden[h];
|
||||
}
|
||||
actions[g][o] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
FuzzyController FuzzyController::mutate(float current_loss, float sigma_scale) const {
|
||||
Genome child_genome = genome.clone();
|
||||
std::normal_distribution<float> noise(0.0f, genome.sigma_gene * sigma_scale);
|
||||
|
||||
for (size_t i = 0; i < child_genome.weights.size(); ++i) {
|
||||
child_genome.weights[i] += noise(rng_);
|
||||
}
|
||||
|
||||
FuzzyController child(child_genome);
|
||||
child.origin = "mutation";
|
||||
return child;
|
||||
}
|
||||
|
||||
FuzzyController FuzzyController::crossover(const FuzzyController& partner, bool use_alignment) const {
|
||||
Genome child_genome;
|
||||
std::uniform_real_distribution<float> coin(0.0f, 1.0f);
|
||||
|
||||
for (size_t i = 0; i < child_genome.weights.size(); ++i) {
|
||||
if (use_alignment && genome.gene_success[i] > partner.genome.gene_success[i]) {
|
||||
child_genome.weights[i] = genome.weights[i];
|
||||
} else if (use_alignment && partner.genome.gene_success[i] > genome.gene_success[i]) {
|
||||
child_genome.weights[i] = partner.genome.weights[i];
|
||||
} else {
|
||||
// Uniform crossover
|
||||
child_genome.weights[i] = (coin(rng_) < 0.5f)
|
||||
? genome.weights[i]
|
||||
: partner.genome.weights[i];
|
||||
}
|
||||
child_genome.gene_success[i] = 0.0f;
|
||||
}
|
||||
|
||||
FuzzyController child(child_genome);
|
||||
child.origin = "crossover";
|
||||
return child;
|
||||
}
|
||||
|
||||
FuzzyController FuzzyController::create_orthogonal_child(float intensity) const {
|
||||
Genome child_genome = genome.clone();
|
||||
// Negate a random subset of weights
|
||||
std::uniform_real_distribution<float> coin(0.0f, 1.0f);
|
||||
for (size_t i = 0; i < child_genome.weights.size(); ++i) {
|
||||
if (coin(rng_) < 0.3f * intensity) {
|
||||
child_genome.weights[i] = -child_genome.weights[i];
|
||||
}
|
||||
}
|
||||
|
||||
FuzzyController child(child_genome);
|
||||
child.origin = "phoenix_rebirth";
|
||||
return child;
|
||||
}
|
||||
|
||||
std::pair<FuzzyController, FuzzyController> FuzzyController::banach_tarski_fission(float intensity) const {
|
||||
Genome plus_genome = genome.clone();
|
||||
Genome minus_genome = genome.clone();
|
||||
std::normal_distribution<float> noise(0.0f, 0.1f * intensity);
|
||||
|
||||
for (size_t i = 0; i < genome.weights.size(); ++i) {
|
||||
float delta = noise(rng_);
|
||||
plus_genome.weights[i] += delta;
|
||||
minus_genome.weights[i] -= delta;
|
||||
}
|
||||
|
||||
return {FuzzyController(plus_genome), FuzzyController(minus_genome)};
|
||||
}
|
||||
|
||||
} // namespace fces
|
||||
Reference in New Issue
Block a user