Skip to content
Merged
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
70 changes: 42 additions & 28 deletions examples/weather_prediction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,32 @@
#![allow(unused_comparisons)]

use ndarray::{arr2, Array2};
use rand::{rngs::StdRng, Rng, SeedableRng};
use rust_lstm::loss::MSELoss;
use rust_lstm::models::lstm_network::LSTMNetwork;
use rust_lstm::optimizers::Adam;
use rust_lstm::training::LSTMTrainer;
use rust_lstm::training::{LSTMTrainer, TrainingConfig};

pub const DEMO_WEATHER_DAYS: usize = 80;
pub const DEMO_SEQUENCE_LENGTH: usize = 5;
pub const DEMO_HIDDEN_SIZE: usize = 8;
pub const DEMO_EPOCHS: usize = 4;
pub const DEMO_PRINT_EVERY: usize = 1;
pub const DEMO_NUM_PREDICTIONS: usize = 5;
pub const DEMO_RECENT_DAYS: usize = DEMO_SEQUENCE_LENGTH + DEMO_NUM_PREDICTIONS;
pub const DEMO_RANDOM_SEED: u64 = 42;

/// Weather data with multiple meteorological features
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
struct WeatherData {
date: String,
temperature: f64, // °C
humidity: f64, // %
pressure: f64, // hPa
wind_speed: f64, // km/h
precipitation: f64, // mm
cloud_cover: f64, // %
pub struct WeatherData {
pub date: String,
pub temperature: f64, // °C
pub humidity: f64, // %
pub pressure: f64, // hPa
pub wind_speed: f64, // km/h
pub precipitation: f64, // mm
pub cloud_cover: f64, // %
}

/// Multi-feature weather prediction system
Expand Down Expand Up @@ -154,11 +164,7 @@ impl WeatherPredictor {
let optimizer = Adam::new(0.001);
let mut trainer = LSTMTrainer::new(self.network.clone(), loss_function, optimizer);

// Configure training for quicker demo
let mut config = rust_lstm::training::TrainingConfig::default();
config.epochs = 20; // Reduced from 100 for demo
config.print_every = 5; // Print more frequently
trainer = trainer.with_config(config);
trainer = trainer.with_config(weather_training_config());

trainer.train(train_data, Some(val_data));

Expand Down Expand Up @@ -192,8 +198,17 @@ impl WeatherPredictor {
}
}

pub fn weather_training_config() -> TrainingConfig {
TrainingConfig {
epochs: DEMO_EPOCHS,
print_every: DEMO_PRINT_EVERY,
..TrainingConfig::default()
}
}

/// Generate realistic weather data with seasonal patterns
fn generate_weather_data(days: usize) -> Vec<WeatherData> {
pub fn generate_weather_data(days: usize) -> Vec<WeatherData> {
let mut rng = StdRng::seed_from_u64(DEMO_RANDOM_SEED);
let mut data = Vec::new();

for i in 0..days {
Expand All @@ -203,23 +218,23 @@ fn generate_weather_data(days: usize) -> Vec<WeatherData> {
let seasonal_temp = 15.0 + 10.0 * (2.0 * std::f64::consts::PI * day_of_year / 365.0).sin();

// Daily temperature variation with some randomness
let daily_variation = (rand::random::<f64>() - 0.5) * 6.0;
let daily_variation = (rng.gen::<f64>() - 0.5) * 6.0;
let temperature = seasonal_temp + daily_variation;

// Humidity inversely correlated with temperature
let humidity = 70.0 - (temperature - 15.0) * 2.0 + (rand::random::<f64>() - 0.5) * 20.0;
let humidity = 70.0 - (temperature - 15.0) * 2.0 + (rng.gen::<f64>() - 0.5) * 20.0;
let humidity = humidity.clamp(20.0, 95.0);

// Pressure with weather patterns
let pressure = 1013.25 + (rand::random::<f64>() - 0.5) * 30.0;
let pressure = 1013.25 + (rng.gen::<f64>() - 0.5) * 30.0;

// Wind speed with some correlation to pressure changes
let wind_speed = 10.0 + (rand::random::<f64>() * 15.0);
let wind_speed = 10.0 + (rng.gen::<f64>() * 15.0);

// Precipitation probability based on humidity and pressure
let precip_prob = (humidity - 50.0) / 100.0 + (1020.0 - pressure) / 50.0;
let precipitation = if rand::random::<f64>() < precip_prob.max(0.0) {
rand::random::<f64>() * 15.0 // 0-15mm
let precipitation = if rng.gen::<f64>() < precip_prob.max(0.0) {
rng.gen::<f64>() * 15.0 // 0-15mm
} else {
0.0
};
Expand Down Expand Up @@ -248,7 +263,7 @@ fn main() {
println!("===========================================\n");

// Generate synthetic weather data
let weather_data = generate_weather_data(365); // One year of data
let weather_data = generate_weather_data(DEMO_WEATHER_DAYS);
println!(
"🌍 Generated {} days of synthetic weather data",
weather_data.len()
Expand All @@ -268,16 +283,15 @@ fn main() {
}

// Create and train predictor
let mut predictor = WeatherPredictor::new(7, 64); // 7-day sequences, 64 hidden units
let mut predictor = WeatherPredictor::new(DEMO_SEQUENCE_LENGTH, DEMO_HIDDEN_SIZE);
predictor.train(&weather_data, 0.2); // 80% train, 20% validation

// Make temperature predictions
println!("\n🔮 Temperature predictions for next 5 days:");
let recent_data = &weather_data[weather_data.len() - 20..]; // Last 20 days
let recent_data = &weather_data[weather_data.len() - DEMO_RECENT_DAYS..];

for i in 7..12 {
// Predict for days 8-12 of recent data
let input_data = &recent_data[i - 7..i];
for i in DEMO_SEQUENCE_LENGTH..DEMO_SEQUENCE_LENGTH + DEMO_NUM_PREDICTIONS {
let input_data = &recent_data[i - DEMO_SEQUENCE_LENGTH..i];
if let Some(predicted_temp) = predictor.predict_temperature(input_data) {
let actual_temp = recent_data[i].temperature;
let error = (predicted_temp - actual_temp).abs();
Expand Down
75 changes: 75 additions & 0 deletions tests/example_training_bounds_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ mod advanced_lr_scheduling;
#[path = "../examples/dropout_example.rs"]
mod dropout_example;

#[allow(dead_code)]
#[path = "../examples/weather_prediction.rs"]
mod weather_prediction;

use std::hint::black_box;

#[test]
Expand Down Expand Up @@ -387,3 +391,74 @@ fn advanced_lr_scheduling_uses_small_deterministic_fixture() {
"generated fixtures should use the public demo sequence-length bound"
);
}

#[test]
fn weather_prediction_applies_bounded_training_config() {
let config = weather_prediction::weather_training_config();

assert!(
black_box(config.epochs) <= 4,
"weather_prediction should avoid long default training runs"
);
assert!(
black_box(config.print_every) > 0,
"weather_prediction progress logging should stay enabled"
);
assert!(
black_box(config.print_every) <= black_box(config.epochs),
"weather_prediction progress logging should not exceed the epoch budget"
);
assert!(
config.early_stopping.is_none(),
"weather_prediction should avoid hidden early-stopping work"
);
assert!(
black_box(weather_prediction::DEMO_WEATHER_DAYS) <= 80,
"weather_prediction should keep its synthetic dataset bounded"
);
assert!(
black_box(weather_prediction::DEMO_SEQUENCE_LENGTH) <= 5,
"weather_prediction should keep each sequence bounded"
);
assert!(
black_box(weather_prediction::DEMO_HIDDEN_SIZE) <= 8,
"weather_prediction should keep hidden size bounded"
);
assert!(
black_box(weather_prediction::DEMO_RECENT_DAYS)
>= black_box(
weather_prediction::DEMO_SEQUENCE_LENGTH + weather_prediction::DEMO_NUM_PREDICTIONS,
),
"weather_prediction should keep enough recent days for every preview prediction"
);
assert!(
black_box(weather_prediction::DEMO_WEATHER_DAYS)
>= black_box(weather_prediction::DEMO_RECENT_DAYS),
"weather_prediction dataset should cover the preview prediction window"
);
}

#[test]
fn weather_prediction_demo_data_is_reproducible() {
let first = weather_prediction::generate_weather_data(black_box(12));
let second = weather_prediction::generate_weather_data(black_box(12));

assert_eq!(
first, second,
"weather demo fixture should be deterministic"
);
assert!(
first.windows(2).any(|window| window[0] != window[1]),
"weather demo fixture should vary across generated days"
);
assert!(
first.iter().all(|weather| {
weather.humidity >= 20.0
&& weather.humidity <= 95.0
&& weather.cloud_cover >= 0.0
&& weather.cloud_cover <= 100.0
&& weather.precipitation >= 0.0
}),
"generated weather fixture should keep bounded meteorological values"
);
}
Loading