Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
@@ -0,0 +1,702 @@
|
||||
//! # Tier 3: Synthetic Nervous Systems for Environments
|
||||
//!
|
||||
//! Buildings, factories, cities.
|
||||
//!
|
||||
//! ## What Changes
|
||||
//! - Infrastructure becomes a sensing fabric
|
||||
//! - Reflexes manage local events
|
||||
//! - Policy emerges from patterns, not rules
|
||||
//!
|
||||
//! ## Why This Matters
|
||||
//! - Environments respond like organisms
|
||||
//! - Energy, safety, and flow self-regulate
|
||||
//! - Central planning gives way to distributed intelligence
|
||||
//!
|
||||
//! This is exotic but inevitable.
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
/// A location in the environment
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct LocationId(pub String);
|
||||
|
||||
/// A zone grouping multiple locations
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ZoneId(pub String);
|
||||
|
||||
/// Environmental sensor reading
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EnvironmentReading {
|
||||
pub timestamp: u64,
|
||||
pub location: LocationId,
|
||||
pub sensor_type: EnvironmentSensor,
|
||||
pub value: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum EnvironmentSensor {
|
||||
Temperature,
|
||||
Humidity,
|
||||
Light,
|
||||
Occupancy,
|
||||
AirQuality,
|
||||
Noise,
|
||||
Motion,
|
||||
Energy,
|
||||
Water,
|
||||
}
|
||||
|
||||
/// Environmental actuator command
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EnvironmentAction {
|
||||
pub location: LocationId,
|
||||
pub actuator: EnvironmentActuator,
|
||||
pub value: f32,
|
||||
pub priority: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EnvironmentActuator {
|
||||
HVAC { mode: HVACMode },
|
||||
Lighting { brightness: f32 },
|
||||
Ventilation { flow_rate: f32 },
|
||||
Shading { position: f32 },
|
||||
DoorLock { locked: bool },
|
||||
Alarm { active: bool },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HVACMode {
|
||||
Off,
|
||||
Heating(f32),
|
||||
Cooling(f32),
|
||||
Ventilation,
|
||||
}
|
||||
|
||||
/// Local reflex for immediate environmental response
|
||||
pub struct LocalEnvironmentReflex {
|
||||
pub location: LocationId,
|
||||
pub sensor_type: EnvironmentSensor,
|
||||
pub threshold_low: f32,
|
||||
pub threshold_high: f32,
|
||||
pub action_low: EnvironmentActuator,
|
||||
pub action_high: EnvironmentActuator,
|
||||
pub hysteresis: f32,
|
||||
pub last_state: i8, // -1 = below, 0 = normal, 1 = above
|
||||
}
|
||||
|
||||
impl LocalEnvironmentReflex {
|
||||
pub fn new(
|
||||
location: LocationId,
|
||||
sensor_type: EnvironmentSensor,
|
||||
threshold_low: f32,
|
||||
threshold_high: f32,
|
||||
action_low: EnvironmentActuator,
|
||||
action_high: EnvironmentActuator,
|
||||
) -> Self {
|
||||
Self {
|
||||
location,
|
||||
sensor_type,
|
||||
threshold_low,
|
||||
threshold_high,
|
||||
action_low,
|
||||
action_high,
|
||||
hysteresis: 0.5,
|
||||
last_state: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if reflex should fire
|
||||
pub fn check(&mut self, reading: &EnvironmentReading) -> Option<EnvironmentAction> {
|
||||
if reading.location != self.location || reading.sensor_type != self.sensor_type {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Apply hysteresis
|
||||
let effective_low = if self.last_state == -1 {
|
||||
self.threshold_low + self.hysteresis
|
||||
} else {
|
||||
self.threshold_low
|
||||
};
|
||||
|
||||
let effective_high = if self.last_state == 1 {
|
||||
self.threshold_high - self.hysteresis
|
||||
} else {
|
||||
self.threshold_high
|
||||
};
|
||||
|
||||
if reading.value < effective_low && self.last_state != -1 {
|
||||
self.last_state = -1;
|
||||
Some(EnvironmentAction {
|
||||
location: self.location.clone(),
|
||||
actuator: self.action_low.clone(),
|
||||
value: reading.value,
|
||||
priority: 1,
|
||||
})
|
||||
} else if reading.value > effective_high && self.last_state != 1 {
|
||||
self.last_state = 1;
|
||||
Some(EnvironmentAction {
|
||||
location: self.location.clone(),
|
||||
actuator: self.action_high.clone(),
|
||||
value: reading.value,
|
||||
priority: 1,
|
||||
})
|
||||
} else if reading.value >= effective_low && reading.value <= effective_high {
|
||||
self.last_state = 0;
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Zone-level homeostasis controller
|
||||
pub struct ZoneHomeostasis {
|
||||
pub zone: ZoneId,
|
||||
pub locations: Vec<LocationId>,
|
||||
pub target_temperature: f32,
|
||||
pub target_humidity: f32,
|
||||
pub target_light: f32,
|
||||
pub adaptation_rate: f32,
|
||||
/// Learned occupancy pattern (24 hours)
|
||||
pub occupancy_pattern: [f32; 24],
|
||||
pub learning_enabled: bool,
|
||||
}
|
||||
|
||||
impl ZoneHomeostasis {
|
||||
pub fn new(zone: ZoneId, locations: Vec<LocationId>) -> Self {
|
||||
Self {
|
||||
zone,
|
||||
locations,
|
||||
target_temperature: 22.0,
|
||||
target_humidity: 50.0,
|
||||
target_light: 500.0,
|
||||
adaptation_rate: 0.1,
|
||||
occupancy_pattern: [0.0; 24],
|
||||
learning_enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn from occupancy patterns
|
||||
pub fn learn_occupancy(&mut self, hour: usize, occupancy: f32) {
|
||||
if self.learning_enabled && hour < 24 {
|
||||
self.occupancy_pattern[hour] = self.occupancy_pattern[hour]
|
||||
* (1.0 - self.adaptation_rate)
|
||||
+ occupancy * self.adaptation_rate;
|
||||
}
|
||||
}
|
||||
|
||||
/// Predict occupancy for pre-conditioning
|
||||
pub fn predict_occupancy(&self, hour: usize) -> f32 {
|
||||
if hour < 24 {
|
||||
self.occupancy_pattern[hour]
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute zone-level action based on aggregate readings
|
||||
pub fn compute_action(
|
||||
&self,
|
||||
readings: &[EnvironmentReading],
|
||||
hour: usize,
|
||||
) -> Vec<EnvironmentAction> {
|
||||
let mut actions = Vec::new();
|
||||
|
||||
// Filter readings for this zone
|
||||
let zone_readings: Vec<_> = readings
|
||||
.iter()
|
||||
.filter(|r| self.locations.contains(&r.location))
|
||||
.collect();
|
||||
|
||||
if zone_readings.is_empty() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Average temperature
|
||||
let temp_readings: Vec<_> = zone_readings
|
||||
.iter()
|
||||
.filter(|r| r.sensor_type == EnvironmentSensor::Temperature)
|
||||
.collect();
|
||||
|
||||
if !temp_readings.is_empty() {
|
||||
let avg_temp: f32 =
|
||||
temp_readings.iter().map(|r| r.value).sum::<f32>() / temp_readings.len() as f32;
|
||||
|
||||
// Adjust target based on predicted occupancy
|
||||
let predicted_occ = self.predict_occupancy(hour);
|
||||
let effective_target = if predicted_occ > 0.5 {
|
||||
self.target_temperature
|
||||
} else {
|
||||
// Setback when unoccupied
|
||||
self.target_temperature - 2.0
|
||||
};
|
||||
|
||||
let temp_error = avg_temp - effective_target;
|
||||
|
||||
if temp_error.abs() > 1.0 {
|
||||
let mode = if temp_error > 0.0 {
|
||||
HVACMode::Cooling(temp_error.abs().min(5.0))
|
||||
} else {
|
||||
HVACMode::Heating(temp_error.abs().min(5.0))
|
||||
};
|
||||
|
||||
for loc in &self.locations {
|
||||
actions.push(EnvironmentAction {
|
||||
location: loc.clone(),
|
||||
actuator: EnvironmentActuator::HVAC { mode: mode.clone() },
|
||||
value: temp_error,
|
||||
priority: 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Light based on occupancy
|
||||
let occupancy_readings: Vec<_> = zone_readings
|
||||
.iter()
|
||||
.filter(|r| r.sensor_type == EnvironmentSensor::Occupancy)
|
||||
.collect();
|
||||
|
||||
if !occupancy_readings.is_empty() {
|
||||
let occupied = occupancy_readings.iter().any(|r| r.value > 0.5);
|
||||
|
||||
for loc in &self.locations {
|
||||
let brightness = if occupied { 1.0 } else { 0.1 };
|
||||
actions.push(EnvironmentAction {
|
||||
location: loc.clone(),
|
||||
actuator: EnvironmentActuator::Lighting { brightness },
|
||||
value: brightness,
|
||||
priority: 3,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
actions
|
||||
}
|
||||
}
|
||||
|
||||
/// Global workspace for environment-wide coordination
|
||||
pub struct EnvironmentWorkspace {
|
||||
pub capacity: usize,
|
||||
pub items: VecDeque<WorkspaceItem>,
|
||||
pub policies: Vec<EmergentPolicy>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WorkspaceItem {
|
||||
pub zone: ZoneId,
|
||||
pub observation: String,
|
||||
pub salience: f32,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EmergentPolicy {
|
||||
pub name: String,
|
||||
pub trigger_pattern: String,
|
||||
pub action_pattern: String,
|
||||
pub confidence: f32,
|
||||
pub occurrences: u64,
|
||||
}
|
||||
|
||||
impl EnvironmentWorkspace {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
capacity,
|
||||
items: VecDeque::new(),
|
||||
policies: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast observation to workspace
|
||||
pub fn broadcast(&mut self, item: WorkspaceItem) {
|
||||
if self.items.len() >= self.capacity {
|
||||
// Remove lowest salience
|
||||
if let Some(min_idx) = self
|
||||
.items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.min_by(|(_, a), (_, b)| a.salience.partial_cmp(&b.salience).unwrap())
|
||||
.map(|(i, _)| i)
|
||||
{
|
||||
self.items.remove(min_idx);
|
||||
}
|
||||
}
|
||||
self.items.push_back(item);
|
||||
}
|
||||
|
||||
/// Detect emergent patterns
|
||||
pub fn detect_patterns(&mut self) -> Option<EmergentPolicy> {
|
||||
// Look for repeated sequences in workspace
|
||||
let observations: Vec<_> = self.items.iter().map(|i| i.observation.clone()).collect();
|
||||
|
||||
if observations.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Simple pattern: if same observation repeats
|
||||
let last = observations.last()?;
|
||||
let count = observations.iter().filter(|o| *o == last).count();
|
||||
|
||||
if count >= 3 {
|
||||
let policy = EmergentPolicy {
|
||||
name: format!("Pattern_{}", self.policies.len()),
|
||||
trigger_pattern: last.clone(),
|
||||
action_pattern: "coordinate_response".to_string(),
|
||||
confidence: count as f32 / observations.len() as f32,
|
||||
occurrences: 1,
|
||||
};
|
||||
|
||||
// Check if already known
|
||||
if !self
|
||||
.policies
|
||||
.iter()
|
||||
.any(|p| p.trigger_pattern == last.clone())
|
||||
{
|
||||
self.policies.push(policy.clone());
|
||||
return Some(policy);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete synthetic nervous system for an environment
|
||||
pub struct SyntheticNervousSystem {
|
||||
pub name: String,
|
||||
/// Local reflexes (fast, location-specific)
|
||||
pub reflexes: Vec<LocalEnvironmentReflex>,
|
||||
/// Zone homeostasis (medium, zone-level)
|
||||
pub zones: HashMap<ZoneId, ZoneHomeostasis>,
|
||||
/// Global workspace (slow, environment-wide)
|
||||
pub workspace: EnvironmentWorkspace,
|
||||
/// Current time
|
||||
pub timestamp: u64,
|
||||
/// Action history
|
||||
pub action_log: Vec<(u64, EnvironmentAction)>,
|
||||
}
|
||||
|
||||
impl SyntheticNervousSystem {
|
||||
pub fn new(name: &str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
reflexes: Vec::new(),
|
||||
zones: HashMap::new(),
|
||||
workspace: EnvironmentWorkspace::new(7),
|
||||
timestamp: 0,
|
||||
action_log: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a zone
|
||||
pub fn add_zone(&mut self, zone_id: &str, locations: Vec<&str>) {
|
||||
let zone = ZoneId(zone_id.to_string());
|
||||
let locs: Vec<_> = locations
|
||||
.iter()
|
||||
.map(|l| LocationId(l.to_string()))
|
||||
.collect();
|
||||
|
||||
self.zones
|
||||
.insert(zone.clone(), ZoneHomeostasis::new(zone, locs));
|
||||
}
|
||||
|
||||
/// Add a local reflex
|
||||
pub fn add_reflex(&mut self, reflex: LocalEnvironmentReflex) {
|
||||
self.reflexes.push(reflex);
|
||||
}
|
||||
|
||||
/// Process sensor readings through the nervous system
|
||||
pub fn process(&mut self, readings: Vec<EnvironmentReading>) -> Vec<EnvironmentAction> {
|
||||
self.timestamp += 1;
|
||||
let hour = ((self.timestamp / 60) % 24) as usize;
|
||||
|
||||
let mut actions = Vec::new();
|
||||
|
||||
// 1. Local reflexes (fastest)
|
||||
for reflex in &mut self.reflexes {
|
||||
for reading in &readings {
|
||||
if let Some(action) = reflex.check(reading) {
|
||||
actions.push(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If reflexes fired, skip higher levels
|
||||
if !actions.is_empty() {
|
||||
for action in &actions {
|
||||
self.action_log.push((self.timestamp, action.clone()));
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
// 2. Zone homeostasis (medium)
|
||||
for (_, zone) in &mut self.zones {
|
||||
// Learn occupancy
|
||||
for reading in &readings {
|
||||
if reading.sensor_type == EnvironmentSensor::Occupancy
|
||||
&& zone.locations.contains(&reading.location)
|
||||
{
|
||||
zone.learn_occupancy(hour, reading.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute zone actions
|
||||
let zone_actions = zone.compute_action(&readings, hour);
|
||||
actions.extend(zone_actions);
|
||||
}
|
||||
|
||||
// 3. Global workspace (slowest, pattern detection)
|
||||
for reading in &readings {
|
||||
if reading.value > 0.8 || reading.value < 0.2 {
|
||||
// Significant observation
|
||||
self.workspace.broadcast(WorkspaceItem {
|
||||
zone: ZoneId("global".to_string()),
|
||||
observation: format!(
|
||||
"{:?}_{}",
|
||||
reading.sensor_type,
|
||||
if reading.value > 0.5 { "high" } else { "low" }
|
||||
),
|
||||
salience: reading.value.abs(),
|
||||
timestamp: self.timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Detect emergent patterns
|
||||
if let Some(policy) = self.workspace.detect_patterns() {
|
||||
println!(
|
||||
" [EMERGENT] New policy: {} (confidence: {:.2})",
|
||||
policy.name, policy.confidence
|
||||
);
|
||||
}
|
||||
|
||||
for action in &actions {
|
||||
self.action_log.push((self.timestamp, action.clone()));
|
||||
}
|
||||
|
||||
actions
|
||||
}
|
||||
|
||||
/// Get system status
|
||||
pub fn status(&self) -> EnvironmentStatus {
|
||||
let learned_patterns = self.workspace.policies.len();
|
||||
|
||||
let zone_states: HashMap<_, _> = self
|
||||
.zones
|
||||
.iter()
|
||||
.map(|(id, zone)| {
|
||||
(
|
||||
id.clone(),
|
||||
ZoneState {
|
||||
target_temp: zone.target_temperature,
|
||||
occupancy_learned: zone.occupancy_pattern.iter().sum::<f32>() > 0.0,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
EnvironmentStatus {
|
||||
timestamp: self.timestamp,
|
||||
active_reflexes: self.reflexes.len(),
|
||||
zones: self.zones.len(),
|
||||
learned_patterns,
|
||||
zone_states,
|
||||
recent_actions: self.action_log.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EnvironmentStatus {
|
||||
pub timestamp: u64,
|
||||
pub active_reflexes: usize,
|
||||
pub zones: usize,
|
||||
pub learned_patterns: usize,
|
||||
pub zone_states: HashMap<ZoneId, ZoneState>,
|
||||
pub recent_actions: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ZoneState {
|
||||
pub target_temp: f32,
|
||||
pub occupancy_learned: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("=== Tier 3: Synthetic Nervous Systems for Environments ===\n");
|
||||
|
||||
let mut building = SyntheticNervousSystem::new("Smart Building");
|
||||
|
||||
// Add zones
|
||||
building.add_zone("office_north", vec!["room_101", "room_102", "room_103"]);
|
||||
building.add_zone("office_south", vec!["room_201", "room_202"]);
|
||||
building.add_zone("lobby", vec!["entrance", "reception"]);
|
||||
|
||||
// Add local reflexes
|
||||
building.add_reflex(LocalEnvironmentReflex::new(
|
||||
LocationId("room_101".to_string()),
|
||||
EnvironmentSensor::Temperature,
|
||||
18.0,
|
||||
28.0,
|
||||
EnvironmentActuator::HVAC {
|
||||
mode: HVACMode::Heating(3.0),
|
||||
},
|
||||
EnvironmentActuator::HVAC {
|
||||
mode: HVACMode::Cooling(3.0),
|
||||
},
|
||||
));
|
||||
|
||||
building.add_reflex(LocalEnvironmentReflex::new(
|
||||
LocationId("entrance".to_string()),
|
||||
EnvironmentSensor::Motion,
|
||||
0.0,
|
||||
0.5,
|
||||
EnvironmentActuator::Lighting { brightness: 0.2 },
|
||||
EnvironmentActuator::Lighting { brightness: 1.0 },
|
||||
));
|
||||
|
||||
println!("Building initialized:");
|
||||
let status = building.status();
|
||||
println!(" Zones: {}", status.zones);
|
||||
println!(" Active reflexes: {}", status.active_reflexes);
|
||||
|
||||
// Simulate a day
|
||||
println!("\nSimulating 24 hours...");
|
||||
for hour in 0..24 {
|
||||
for minute in 0..60 {
|
||||
let timestamp = hour * 60 + minute;
|
||||
|
||||
// Generate readings based on time of day
|
||||
let occupied = (hour >= 8 && hour <= 18) && (minute % 5 == 0);
|
||||
let temp = 20.0 + 4.0 * ((hour as f32 / 24.0) * PI).sin();
|
||||
|
||||
let readings = vec![
|
||||
EnvironmentReading {
|
||||
timestamp,
|
||||
location: LocationId("room_101".to_string()),
|
||||
sensor_type: EnvironmentSensor::Temperature,
|
||||
value: temp,
|
||||
},
|
||||
EnvironmentReading {
|
||||
timestamp,
|
||||
location: LocationId("room_101".to_string()),
|
||||
sensor_type: EnvironmentSensor::Occupancy,
|
||||
value: if occupied { 1.0 } else { 0.0 },
|
||||
},
|
||||
EnvironmentReading {
|
||||
timestamp,
|
||||
location: LocationId("entrance".to_string()),
|
||||
sensor_type: EnvironmentSensor::Motion,
|
||||
value: if occupied && minute % 15 == 0 {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let actions = building.process(readings);
|
||||
|
||||
if hour % 4 == 0 && minute == 0 {
|
||||
println!(
|
||||
" Hour {}: {} actions, temp={:.1}°C, occupied={}",
|
||||
hour,
|
||||
actions.len(),
|
||||
temp,
|
||||
occupied
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
let status = building.status();
|
||||
println!("\n=== End of Day Status ===");
|
||||
println!(" Total actions taken: {}", status.recent_actions);
|
||||
println!(" Emergent policies learned: {}", status.learned_patterns);
|
||||
println!(" Zone states:");
|
||||
for (zone, state) in &status.zone_states {
|
||||
println!(
|
||||
" {:?}: target={:.1}°C, occupancy_learned={}",
|
||||
zone.0, state.target_temp, state.occupancy_learned
|
||||
);
|
||||
}
|
||||
|
||||
println!("\n=== Key Benefits ===");
|
||||
println!("- Infrastructure becomes a sensing fabric");
|
||||
println!("- Local reflexes handle immediate events");
|
||||
println!("- Zone homeostasis manages comfort autonomously");
|
||||
println!("- Policies emerge from patterns, not rules");
|
||||
println!("- Energy, safety, and flow self-regulate");
|
||||
println!("\nThis is exotic but inevitable.");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_local_reflex() {
|
||||
let mut reflex = LocalEnvironmentReflex::new(
|
||||
LocationId("test".to_string()),
|
||||
EnvironmentSensor::Temperature,
|
||||
18.0,
|
||||
28.0,
|
||||
EnvironmentActuator::HVAC {
|
||||
mode: HVACMode::Heating(1.0),
|
||||
},
|
||||
EnvironmentActuator::HVAC {
|
||||
mode: HVACMode::Cooling(1.0),
|
||||
},
|
||||
);
|
||||
|
||||
// Cold triggers heating
|
||||
let reading = EnvironmentReading {
|
||||
timestamp: 0,
|
||||
location: LocationId("test".to_string()),
|
||||
sensor_type: EnvironmentSensor::Temperature,
|
||||
value: 15.0,
|
||||
};
|
||||
|
||||
let action = reflex.check(&reading);
|
||||
assert!(action.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zone_homeostasis() {
|
||||
let mut zone = ZoneHomeostasis::new(
|
||||
ZoneId("test".to_string()),
|
||||
vec![LocationId("room1".to_string())],
|
||||
);
|
||||
|
||||
// Learn occupancy pattern
|
||||
for _ in 0..10 {
|
||||
zone.learn_occupancy(10, 1.0); // 10am occupied
|
||||
zone.learn_occupancy(22, 0.0); // 10pm empty
|
||||
}
|
||||
|
||||
assert!(zone.predict_occupancy(10) > 0.5);
|
||||
assert!(zone.predict_occupancy(22) < 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workspace_patterns() {
|
||||
let mut workspace = EnvironmentWorkspace::new(7);
|
||||
|
||||
// Add repeated observation
|
||||
for _ in 0..5 {
|
||||
workspace.broadcast(WorkspaceItem {
|
||||
zone: ZoneId("test".to_string()),
|
||||
observation: "Temperature_high".to_string(),
|
||||
salience: 1.0,
|
||||
timestamp: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let pattern = workspace.detect_patterns();
|
||||
assert!(pattern.is_some());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user