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