use alec::context::{
Context, ContextConfig, EvolutionConfig, Pattern, PreloadDictEntry, PreloadFile,
PreloadPredictionModel, PreloadPredictionType, PreloadStatistics,
};
use alec::protocol::RawData;
use std::path::Path;
struct PreloadConfig {
sensor_type: &'static str,
min_value: f64,
max_value: f64,
typical_value: f64,
noise_amplitude: f64,
training_samples: usize,
prediction_type: PreloadPredictionType,
prediction_coefficients: Vec<f64>,
period_samples: u32,
}
fn main() {
println!("=== ALEC Demo Preload Generator ===\n");
let output_dir = Path::new("contexts/demo");
std::fs::create_dir_all(output_dir).expect("Failed to create output directory");
generate_temperature_preload(output_dir);
generate_humidity_preload(output_dir);
generate_counter_preload(output_dir);
println!("\n=== Verification ===\n");
verify_preload(output_dir.join("demo_temperature_v1.alec-context"));
verify_preload(output_dir.join("demo_humidity_v1.alec-context"));
verify_preload(output_dir.join("demo_counter_v1.alec-context"));
println!("\n=== Done ===");
println!("\nPreloads are ready in: {}", output_dir.display());
println!("\nUsage example:");
println!(" let ctx = Context::load_from_file(Path::new(\"contexts/demo/demo_temperature_v1.alec-context\"))?;");
}
fn generate_temperature_preload(output_dir: &Path) {
println!("Generating: demo_temperature_v1.alec-context");
let config = PreloadConfig {
sensor_type: "temperature",
min_value: 20.0,
max_value: 25.0,
typical_value: 22.5,
noise_amplitude: 0.1,
training_samples: 10000,
prediction_type: PreloadPredictionType::MovingAverage,
prediction_coefficients: vec![3.0], period_samples: 0,
};
let ctx_config = ContextConfig {
evolution: EvolutionConfig {
enabled: false,
..Default::default()
},
..Default::default()
};
let mut ctx = Context::with_config(ctx_config);
let delta_patterns: Vec<i16> = vec![
0, 1, -1, 2, -2, 5, -5, 10, -10, ];
for delta in delta_patterns {
let bytes = delta.to_le_bytes().to_vec();
let _ = ctx.register_pattern(Pattern::new(bytes));
}
let mut value = config.typical_value;
let mut drift_direction = 1.0;
for i in 0..config.training_samples {
let noise = (simple_random(i as u64) - 0.5) * 2.0 * config.noise_amplitude;
if value >= config.max_value - 0.5 {
drift_direction = -1.0;
} else if value <= config.min_value + 0.5 {
drift_direction = 1.0;
}
let drift = drift_direction * 0.001;
value = (value + drift + noise).clamp(config.min_value, config.max_value);
ctx.observe(&RawData::new(value, i as u64));
}
let preload = build_preload(&ctx, &config);
let path = output_dir.join("demo_temperature_v1.alec-context");
preload
.save_to_file(&path)
.expect("Failed to save temperature preload");
println!(" - Saved: {}", path.display());
println!(" - Training samples: {}", config.training_samples);
println!(" - Patterns: {}", ctx.pattern_count());
}
fn generate_humidity_preload(output_dir: &Path) {
println!("Generating: demo_humidity_v1.alec-context");
let config = PreloadConfig {
sensor_type: "humidity",
min_value: 40.0,
max_value: 60.0,
typical_value: 50.0,
noise_amplitude: 2.0,
training_samples: 10000,
prediction_type: PreloadPredictionType::Periodic,
prediction_coefficients: vec![],
period_samples: 86400, };
let ctx_config = ContextConfig {
evolution: EvolutionConfig {
enabled: false,
..Default::default()
},
..Default::default()
};
let mut ctx = Context::with_config(ctx_config);
let delta_patterns: Vec<i16> = vec![
0, 10, -10, 20, -20, 50, -50, 100, -100, ];
for delta in delta_patterns {
let bytes = delta.to_le_bytes().to_vec();
let _ = ctx.register_pattern(Pattern::new(bytes));
}
let samples_per_day = 86400; let cycle_amplitude = 5.0;
for i in 0..config.training_samples {
let day_progress = (i % samples_per_day) as f64 / samples_per_day as f64;
let cycle_value = (day_progress * 2.0 * std::f64::consts::PI).sin() * cycle_amplitude;
let noise = (simple_random(i as u64) - 0.5) * 2.0 * config.noise_amplitude;
let value =
(config.typical_value + cycle_value + noise).clamp(config.min_value, config.max_value);
ctx.observe(&RawData::new(value, i as u64));
}
let preload = build_preload(&ctx, &config);
let path = output_dir.join("demo_humidity_v1.alec-context");
preload
.save_to_file(&path)
.expect("Failed to save humidity preload");
println!(" - Saved: {}", path.display());
println!(" - Training samples: {}", config.training_samples);
println!(" - Patterns: {}", ctx.pattern_count());
}
fn generate_counter_preload(output_dir: &Path) {
println!("Generating: demo_counter_v1.alec-context");
let config = PreloadConfig {
sensor_type: "counter",
min_value: 0.0,
max_value: 1_000_000.0,
typical_value: 0.0,
noise_amplitude: 0.0,
training_samples: 10000,
prediction_type: PreloadPredictionType::Linear,
prediction_coefficients: vec![1.0, 0.0], period_samples: 0,
};
let ctx_config = ContextConfig {
evolution: EvolutionConfig {
enabled: false,
..Default::default()
},
..Default::default()
};
let mut ctx = Context::with_config(ctx_config);
let patterns: Vec<Vec<u8>> = vec![
1i64.to_le_bytes().to_vec(), 0i64.to_le_bytes().to_vec(), vec![0xFF; 8], ];
for pattern in patterns {
let _ = ctx.register_pattern(Pattern::new(pattern));
}
let mut counter: i64 = 0;
let reset_probability = 0.001;
for i in 0..config.training_samples {
if simple_random(i as u64 + 1000) < reset_probability {
counter = 0;
} else {
counter += 1;
}
ctx.observe(&RawData::new(counter as f64, i as u64));
}
let preload = build_preload(&ctx, &config);
let path = output_dir.join("demo_counter_v1.alec-context");
preload
.save_to_file(&path)
.expect("Failed to save counter preload");
println!(" - Saved: {}", path.display());
println!(" - Training samples: {}", config.training_samples);
println!(" - Patterns: {}", ctx.pattern_count());
}
fn build_preload(ctx: &Context, config: &PreloadConfig) -> PreloadFile {
let mut dictionary: Vec<PreloadDictEntry> = Vec::new();
for (&code, pattern) in ctx.patterns_iter() {
if code <= u16::MAX as u32 {
dictionary.push(PreloadDictEntry {
pattern: pattern.data.clone(),
code: code as u16,
frequency: pattern.frequency as u32,
});
}
}
dictionary.sort_by_key(|e| e.code);
let statistics = PreloadStatistics {
mean: config.typical_value,
variance: config.noise_amplitude.powi(2),
min_observed: config.min_value,
max_observed: config.max_value,
min_expected: config.min_value - config.noise_amplitude,
max_expected: config.max_value + config.noise_amplitude,
recent_values: vec![config.typical_value; 3],
};
let prediction = PreloadPredictionModel {
model_type: config.prediction_type,
coefficients: config.prediction_coefficients.clone(),
period_samples: config.period_samples,
};
PreloadFile {
format_version: 1,
context_version: ctx.version(),
sensor_type: config.sensor_type.to_string(),
created_timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0),
training_samples: config.training_samples as u64,
dictionary,
statistics,
prediction,
}
}
fn simple_random(seed: u64) -> f64 {
let a: u64 = 6364136223846793005;
let c: u64 = 1442695040888963407;
let m: u64 = u64::MAX;
let result = seed.wrapping_mul(a).wrapping_add(c) % m;
result as f64 / m as f64
}
fn verify_preload<P: AsRef<Path>>(path: P) {
let path = path.as_ref();
print!("Verifying: {} ... ", path.display());
match PreloadFile::load_from_file(path) {
Ok(preload) => {
assert!(
!preload.sensor_type.is_empty(),
"Sensor type should not be empty"
);
assert!(preload.format_version == 1, "Format version should be 1");
assert!(preload.training_samples > 0, "Should have training samples");
assert!(
preload.statistics.min_observed <= preload.statistics.max_observed,
"Min should be <= max"
);
assert!(
preload.statistics.mean >= preload.statistics.min_observed
&& preload.statistics.mean <= preload.statistics.max_observed,
"Mean should be within observed range"
);
println!("OK");
println!(" Sensor type: {}", preload.sensor_type);
println!(" Context version: {}", preload.context_version);
println!(" Training samples: {}", preload.training_samples);
println!(" Dictionary entries: {}", preload.dictionary.len());
println!(" Prediction model: {:?}", preload.prediction.model_type);
}
Err(e) => {
println!("FAILED");
println!(" Error: {}", e);
}
}
}