Skip to main content

elevator_core/
energy.rs

1//! Simplified energy modeling for elevators.
2//!
3//! Provides an [`EnergyProfile`](crate::energy::EnergyProfile) that
4//! parameterizes per-tick energy costs and an
5//! [`EnergyMetrics`](crate::energy::EnergyMetrics) accumulator for tracking
6//! consumption and regeneration over time. The pure function
7//! `compute_tick_energy` calculates consumed and regenerated energy for a
8//! single tick.
9
10use serde::{Deserialize, Serialize};
11
12/// Per-elevator energy cost parameters.
13///
14/// Attach to an elevator entity via [`World::set_energy_profile`](crate::world::World::set_energy_profile)
15/// to enable energy tracking for that car. The energy system automatically
16/// initializes [`EnergyMetrics`] if not already present.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct EnergyProfile {
19    /// Energy consumed per tick while idle (doors closed, stationary).
20    pub idle_cost_per_tick: f64,
21    /// Base energy consumed per tick while moving.
22    pub move_cost_per_tick: f64,
23    /// Multiplier applied to current load weight when moving.
24    pub weight_factor: f64,
25    /// Fraction of consumed energy regenerated when descending (0.0 - 1.0).
26    pub regen_factor: f64,
27}
28
29impl EnergyProfile {
30    /// Create a new energy profile with the given parameters.
31    #[must_use]
32    pub const fn new(
33        idle_cost_per_tick: f64,
34        move_cost_per_tick: f64,
35        weight_factor: f64,
36        regen_factor: f64,
37    ) -> Self {
38        Self {
39            idle_cost_per_tick,
40            move_cost_per_tick,
41            weight_factor,
42            regen_factor,
43        }
44    }
45
46    /// Energy consumed per tick while idle.
47    #[must_use]
48    pub const fn idle_cost_per_tick(&self) -> f64 {
49        self.idle_cost_per_tick
50    }
51
52    /// Base energy consumed per tick while moving.
53    #[must_use]
54    pub const fn move_cost_per_tick(&self) -> f64 {
55        self.move_cost_per_tick
56    }
57
58    /// Multiplier applied to current load weight when moving.
59    #[must_use]
60    pub const fn weight_factor(&self) -> f64 {
61        self.weight_factor
62    }
63
64    /// Fraction of consumed energy regenerated when descending.
65    #[must_use]
66    pub const fn regen_factor(&self) -> f64 {
67        self.regen_factor
68    }
69}
70
71/// Accumulated energy metrics for a single elevator.
72#[derive(Debug, Clone, Default, Serialize, Deserialize)]
73pub struct EnergyMetrics {
74    /// Total energy consumed over the simulation.
75    pub(crate) total_consumed: f64,
76    /// Total energy regenerated over the simulation.
77    pub(crate) total_regenerated: f64,
78    /// Number of ticks with energy activity recorded.
79    pub(crate) ticks_tracked: u32,
80}
81
82impl EnergyMetrics {
83    /// Total energy consumed.
84    #[must_use]
85    pub const fn total_consumed(&self) -> f64 {
86        self.total_consumed
87    }
88
89    /// Total energy regenerated.
90    #[must_use]
91    pub const fn total_regenerated(&self) -> f64 {
92        self.total_regenerated
93    }
94
95    /// Number of ticks with energy activity recorded.
96    #[must_use]
97    pub const fn ticks_tracked(&self) -> u32 {
98        self.ticks_tracked
99    }
100
101    /// Net energy: consumed minus regenerated.
102    #[must_use]
103    pub const fn net_energy(&self) -> f64 {
104        self.total_consumed - self.total_regenerated
105    }
106
107    /// Record a tick's energy consumption and regeneration.
108    pub(crate) fn record(&mut self, consumed: f64, regenerated: f64) {
109        self.total_consumed += consumed;
110        self.total_regenerated += regenerated;
111        self.ticks_tracked += 1;
112    }
113}
114
115/// Compute consumed and regenerated energy for a single tick.
116///
117/// Returns `(consumed, regenerated)`.
118///
119/// - Idle: consumed = `idle_cost_per_tick`, regenerated = 0.
120/// - Moving: consumed = `move_cost_per_tick + weight_factor * current_load`.
121/// - Moving downward (velocity < 0): regenerated = `consumed * regen_factor`.
122#[must_use]
123pub(crate) fn compute_tick_energy(
124    profile: &EnergyProfile,
125    is_moving: bool,
126    current_load: f64,
127    velocity: f64,
128) -> (f64, f64) {
129    if !is_moving {
130        return (profile.idle_cost_per_tick, 0.0);
131    }
132
133    let consumed = profile
134        .weight_factor
135        .mul_add(current_load, profile.move_cost_per_tick);
136    let regenerated = if velocity < 0.0 {
137        consumed * profile.regen_factor
138    } else {
139        0.0
140    };
141
142    (consumed, regenerated)
143}