Skip to main content

elevator_core/
energy.rs

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